一、Promise
1、谈谈你了解的Promise?
Promise 对象是一个代理对象。它接受你传入的 executor(执行器)作为入参,允许你把异步任务的成功和失败分别绑定到对应的处理方法上去。一个 Promise 实例有三种状态:
• pending 状态,表示进行中。这是 Promise 实例创建后的一个初始态;
• fulfilled 状态,表示成功完成。这是我们在执行器中调用 resolve 后,达成的状态;
• rejected 状态,表示操作失败、被拒绝。这是我们在执行器中调用 reject后,达成的状态;
Promise实例的状态是可以改变的,但它只允许被改变一次。当我们的实例状态从 pending 切换为 rejected 后,就无法再扭转为 fulfilled,反之同理。当 Promise 的状态为 resolved 时,会触发其对应的 then 方法入参里的 onfulfilled 函数;当 Promise 的状态为 rejected 时,会触发其对应的 then 方法入参里的 onrejected 函数。
// 手写Promise
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
class MyPromise {
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 存储成功回调函数
onFulfilledCallbacks = [];
// 存储失败回调函数
onRejectedCallbacks = [];
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason};
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
})
return promise2;
}
// resolve 静态方法
static resolve (parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter;
}
// 转成常规方式
return new MyPromise(resolve => {
resolve(parameter);
});
}
// reject 静态方法
static reject (reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
}
function resolvePromise(promise2, x, resolve, reject) {
// 如果相等了,说明return的是自己,抛出类型错误并返回
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #'))
}
// 判断x是不是 MyPromise 实例对象
if(x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else{
// 普通值
resolve(x)
}
}
2、Promise的方法有哪些?
(1)Promise.all() 方法接受一个数组作为参数, p1 、 p2 、 p3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。另外, Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p 的状态由 p1 、 p2 、 p3 决定,分成两种情况。
(1)只有 p1 、 p2 、 p3 的状态都变成 fulfilled , p 的状态才会变成 fulfilled ,此时 p1 、 p2 、 p3 的返回值组成一个数组,传递给 p 的回调函数。
(2)只要 p1 、 p2 、 p3 之中有一个被 rejected , p 的状态就变成 rejected ,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。
//手写Promise.all
Promise._all = function (promises) {
return new Promise((resolve, reject) => {
const results =[];
for (let i = 0;i< promises.length;i++) {
const promise=promises[i];
// 这里使用 Promise.resolve 包了一下,以防传递了 non-promise Promise.resolve(promise).then(res => {
results.push(res);
if (results.length=== promises.length) resolve(results);
}).catch(reject);
}
})
}
Promise._all([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error)
})
(2)Promise.race()和Promise.all()类似,不同之处是只要 p1 、 p2 、 p3 之中有一个实例率先改变状态, p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。
//手写Promise.race
Promise._race =function (promises) {
return new Promise((resolve, reject) => {
promises.forEach(promise => {
Promise.resolve(promise).then(res => {
resolve(res);
}, reson =>{
reject(reson);
})
})
});
Promise._race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(res=>{
console.log(res);
},res=>{
console.log(res);
});
(3)Promise.allSettled() 方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是 fulfilled 还是 rejected ,包装实例才会结束。该方法由 ES2020 引入。
该方法返回的新的 Promise 实例,一旦结束,状态总是 fulfilled ,不会变成 rejected 。它的监听函数接收到的参数是数组 results 。该数组的每个成员都是一个对象,对应传入 Promise.allSettled() 的两个 Promise 实例。每个对象都有 status 属性,该属性的值只可能是字符串 fulfilled 或字符串 rejected 。 fulfilled 时,对象有 value 属性, rejected 时有 reason 属性,对应两种状态的返回值。
Promise.allSettled = function (promises) {
return new Promise(resolve => {
const data = [], len = promises.length;
let count = len;
for (let i = 0; i < len; i += 1) {
const promise = promises[i];
promise.then(res => {
data[i] = { status: 'fulfilled', value: res };
}, error => {
data[i] = { status: 'rejected', reason: error };
}).finally(() => { // promise has been settled
if (!--count) {
resolve(data);
}
});
}
});
}
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
//在all的基础上写allSettled
Promise.allSettled = function (promises) {
return Promise.all(promises.map(p => Promise.resolve(p).then(res => {
return { status: 'fulfilled', value: res }
}, error => {
return { status: 'rejected', reason: error }
})));
};
(4)Promise.any()
Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态。该方法目前是一个第三阶段的提案 。
原型上的方法finally
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
//使用
var promise = new Promise(function(resolve, reject) {
console.log("promise")
window.setTimeout(function(){
if (false){
resolve('huangbiao');
} else {
debugger
reject('error');
}
},1000)
}).then(function(){
console.log('success');
}).catch(function(){
console.log('catch');
}).finally(function(){
console.log('finally');
});
3、Promise真题
//promise的状态一旦变为成功或失败就不会再次改变
const promise = new Promise((resolve, reject) => {
resolve('第 1 次 resolve')
console.log('resolve后的普通逻辑')
reject('error')
resolve('第 2 次 resolve')
})
promise
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
输出:resolve后的普通逻辑
then: 第 1 次 resolve
//Promise的值穿透,then的参数为函数,如果不是函数则会被当做透明
Promise.resolve(1)
.then(Promise.resolve(2))
.then(3)
.then()
.then(console.log)
输出:1
//
setTimeout(() => {
console.log(1);
},0);
new Promise(function(resolve){
resolve();
console.log(2);
}).then(console.log(3))
console.log(4);
// 2 3 4 1
then里传入了一个已调用的函数,而不是函数的引用,所以会被立即执行掉
二、Gernerator简介
传统的编程语言,早有异步编程的解决方案(其实是多任务的解决方案)。其中
有一种叫做“协程”(coroutine),意思是多个线程互相协作,完成异步任务。
协程有点像函数,又有点像线程,它的运行流程大致如下:
第一步,协程 A 开始执行;
第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B;
第三步,(一段时间后)协程 B 交还执行权;
第四步,协程 A 恢复执行;
上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。
举例来说,读取文件的协程写法如下:
function* asyncJob() { // ... var f = yield readFile(fileA); // ...}
上面代码的函数 asyncJob 是一个协程,它的奥妙就在其中的 yield 命令。它表示
执行到此处,执行权将交给其他协程。也就是说,yield 命令是异步两个阶段的
分界线。协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往
后执行。
Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即
暂停执行)。
function* gen(x) {var y = yield x + 2;return y;
}var g = gen(1);g.next() // { value: 3, done: false }g.next(2) // { value: 2, done: false }
next 是返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以
接受参数,向 Generator 函数体内输入数据。
上面代码中,第一个 next 方法的 value 属性,返回表达式 x + 2 的值 3。第二
个 next 方法带有参数 2,这个参数可以传入 Generator 函数,作为 上个阶段 异步
任务的返回结果,被函数体内的变量 y 接收。因此,这一步的
value 属性,返回
的就是 2(变量 y 的值)。
二、async/await
async 函数是Generator和Promise的语法糖。对 Generator 函数的改进,体现在以下四点:
(1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了 co 模块,而 async 函数自带执行器。也就是说, async 函数的执行,与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了 asyncReadFile 函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。
(2)更好的语义。
async 和 await ,比起星号和 yield ,语义更清楚了。 async 表示函数里有异步操作, await 表示紧跟在后面的表达式需要等待结果。
(3)更广的适用性。
co 模块约定, yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
(4)返回值是 Promise。
async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用 then 方法指定下一步的操作。
进一步说, async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖。
错误捕获:
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
用for...of遍历可以继发执行
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
//或reduce
async function dbFuc(db) {
let docs = [{}, {}, {}];
await docs.reduce(async (_, doc) => {
await _;
await db.post(doc);
}, undefined);
}
//foreach遍历出来的会是并发执行
function dbFuc(db) { //这里不需要 async
let docs = [{}, {}, {}];
// 可能得到错误结果,因为这里会并发执行
docs.forEach(async function (doc) {
await db.post(doc);
});
}
实现原理:
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
一道经典面试题:
//输出结果
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');