Promise&Generator&Async 异步函数剖析
提示
对于执行过程,只要理解了JS单线程的EvenLoop事件轮询机制便可迎刃而解。
# Promise
# 概述
- promise 是一个有then方法的对象或者是函数
# Promise States
Promise 必须处于以下三个状态之一:
pending
,fulfilled
或者是rejected
- 如果promise在
pending
状态:可以变成 fulfilled 或者是 rejected - 如果promise在
fulfilled
状态:不会变成其它状态(有成功的value值) - 如果promise在
rejected
状态:不会变成其它状态(有失败的reason)
- 如果promise在
# Promise.prototype.then
then之后会返回一个
promise
,由内部完成实现promise的
then
方法接收两个参数promise.then(onFulfilled, onRejected)
onFulfilled
和onRejected
都是可选参数,必须是函数类型,只能被调用一次- 状态变更后所有的
onFulfilled
和onRejected
都会被调用(多个promise.then(onFulfilled,onRejected))
# Promise.prototype.catch
用于指定出错时的回调,是特殊的then方法,catch之后,可以继续
.then
,效果同then第二个参数- 实现原理
Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); }
1
2
3
# Promise.prototype.finally
不管成功还是失败,都会走到
finally
中,并且finally
之后,还可以继续then
。并且会将值原封不动的传递给后面的then
- 实现原理
Promise.prototype.finally = function (callback) { return this.then((value) => { return Promise.resolve(callback()).then(() => { return value; }); }, (err) => { return Promise.resolve(callback()).then(() => { throw err; }); }); }
1
2
3
4
5
6
7
8
9
10
11
# Promise.resolve
如果 value 是个 thenable 对象,返回的promise会“跟随”这个thenable的对象,采用它的最终状态
如果传入的value本身就是promise对象,那么Promise.resolve将不做任何修改、原封不动地返回这个promise对象。
其他情况,直接返回以该值为成功状态的promise对象。
- 实现原理
Promise.resolve = function (param) { if (param instanceof Promise) { return param; } return new Promise((resolve, reject) => { if (param && typeof param === 'object' && typeof param.then === 'function') { setTimeout(() => { // setTimeout 为模拟异步操作,内部实现原理并非如此 param.then(resolve, reject); }); } else { resolve(param); } }); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Promise.reject
Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
- 实现原理
Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason); }); }
1
2
3
4
5
# Promise.all
参数接收一个数组/可迭代对象,Promise.all(promises) 返回一个promise对象
如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。
如果传入的参数不包含任何 promise,则返回一个异步完成.
所有promise都满足成功状态才会执行
resolve
,否则执行reject
在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
- 实现原理
Promise.all = function (promises) { promises = Array.from(promises);//将可迭代对象转换为数组 return new Promise((resolve, reject) => { let index = 0; let result = []; if (promises.length === 0) { resolve(result); } else { function processValue(i, data) { result[i] = data; if (++index === promises.length) { resolve(result); } } for (let i = 0; i < promises.length; i++) { //promises[i] 可能是普通值 Promise.resolve(promises[i]).then((data) => { processValue(i, data); }, (err) => { reject(err); return; }); } } }); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Promise.race
接收参数同
all
方法Promise.race函数返回一个 Promise
它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
- 实现原理
Promise.race = function (promises) { promises = Array.from(promises);//将可迭代对象转换为数组 return new Promise((resolve, reject) => { if (promises.length === 0) { return; } else { for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then((data) => { resolve(data); return; }, (err) => { reject(err); return; }); } } }); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Generator
# Async
可以理解为Generator的语法糖,用法更简单,语义更明了
- 内置执行器:直接调用函数即可返回最终值,Generator需要手动调用 next() 继续往下执行。
- 更好的语义:async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
- 返回值是Promise:Generator通过yield对任务进行切分返回的是Iterator。
- 更广的适用性:async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。
看下面代码和注释分析
// 正常来说await后面放一个Promise,不过其实后面可以跟任意JavaScript的值,
// 如果不是promise,会被制转为promise,所以await 20效果如下
async function foo() {
const v = await 20;
return v;
}
const p = foo(); //=> Promise
p.then(console.log); // 20
// V8对于await的处理,执行foo()时,
// 它把参数v封装成一个promise,然后会暂停直到promise完成,
// 然后w赋值为已完成的promise,最后async返回了这个值
async function foo(v) {
const w = await v;
return w;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 类比promise方式
// 参考JS的EvenLoop事件轮询机制
// Promise
new Promise((resolve,reject)=>{
console.log('立即/同步执行') //这里是同步执行的
}).then(res=>{
console.log('异步执行') // then会放入js主线程的微任务队列中,即异步
})
// async
async function main(){
console.log('立即/同步执行') //
const data= await fetch() // await后的内容是放在微任务中的异步执行
console.log('等待await执行') //会等待await执行完成后再往下执行该代码
}
2
3
4
5
6
7
8
9
10
11
12
13
# 错误捕获
- 主要理解JS单线程EventLoop的事件轮询机制。
- new Promise 是同步执行的,而then是微任务回调即异步。
- 对于setTimeout这种已经排队到下一次主线程宏任务的执行错误在当前函数内是捕获不到的。不过可以通过promise进行包装通过reject接收处理。
# promise 的异常捕获
外层包裹的
try catch
捕获不到promise
的内部错误promise
内部的错误不会冒泡出来,只有通过promise.catch()
才可以捕获- 代码
// throw new Error function main1() { try { new Promise(() => { throw new Error('promise1 error') }) } catch (e) { console.log(e.message); // 空 } } // reject function main2() { try { Promise.reject('promise2 error'); } catch (e) { console.log(e.message); // 空 } } // setTimeout function main3() { try { setTimeout(() => { throw new Error('async error'); }, 1000); } catch (e) { console.log(e, 'err'); console.log('continue...'); } } // 以上情况都捕获不到错误 // 1. promise内部错误不会冒泡出来 // 2. setTimeout已经进入到下一次的主线程,而main的栈已经退出(异步)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32promise
内部Error
可以通过catch
捕获- 微任务的回调,要确定是否还在同一事件循环中
- 代码
// reject new Promise((reslove, reject) => { reject(); }).catch((e) => console.log('p1 error'));// p1 error // throw new Error new Promise((reslove, reject) => { throw new Error('p2 error') }).catch((e) => console.log('p2 error')); // p2 error // then // then内部错误会传递到下一个then回调,需要通过catch接收 new Promise((reslove, reject) => { reslove(1) }).then(res=>{ throw new Error('p2 error') }).catch((e) => console.log(e)); // p2 error // setTimeout // 主动调用reject抛出错误是可以被then/catch捕获 new Promise((reslove, reject) => { setTimeout(() => { reject('async error'); }); }).catch(e => console.log(e)); // async error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# async/await 的异常捕获
- 它也不会自动去
catch
错误,需要我们自己写try catch
- 这里可以捕获到错误,是因为生成器也有一个“
throw
”状态,当promise
的状态reject
后,会向上被“throw
”执行,然后执行catch
里的代码console.log('e.message', e)
;
const fetchError =()=> new Promise((resolve, reject) => {
setTimeout(() => {
reject('err');
});
});
async function main() {
try {
const res = await fetchError();
console.log('res', res);
} catch (e) {
console.log('e.message', e);
}
}
main(); // e.message err
2
3
4
5
6
7
8
9
10
11
12
13
14
15