es之Promise深入理解

1,309 阅读6分钟

1、await和 async是干什么的

await操作符

  • 等待一个Promise对象
  • 只能再async函数中使用
  • 如果操作符后的表达式不是一个Promise,则返该值本身
async function fun(){
    var val = await 20;
    console.log(val);
}
fun(); 
//20

async function bar(){
    var val = await Promise.resolve(20);
    console.log(val);
}
bar();
//20

async函数

  • 定义异步函数,返回Promise实例,
  • 当 async 函数返回一个值时,Promise 的 resolve 方法负责传递这个值
  • 当 async 函数抛出异常时,Promise 的 reject 方法会传递这个异常值
async function bar(name){
    var subName = await Promise.resolve('fen');
    return name + subName;
}
bar('li').then((res)=>{
    console.log(res);
});

//lifen

2、Promise的链式then()是怎样执行的

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log('p1_1')
}).then(() => {
    console.log('p1_2')
}).then(() => {
    console.log('p1_3')
});

new Promise((resolve) => {
    resolve();
}).then(() => {
    console.log('p2_1')
}).then(() => {
    console.log('p2_2')
}).then(() => {
    console.log('p2_3')
});

结果如下:

p1_1
p2_1
p1_2
p2_2
p1_3
p2_3

Promise是基于微任务的(微任务文章点击链接),async/await可视为 Promise 的语法糖,同样基于微任务实现

分析

  • Promise 多个 then() 链式调用,并不是连续的创建了多个微任务并推入微任务队列,因为 then() 的返回值必然是一个 Promise,而后续的 then() 是上一步 then() 返回的 Promise 的回调
  • 传入 Promise 构造器的执行器函数内部的同步代码执行到 resolve(),将 Promise 的状态改变为Promise {: undefined},然后 then 中传入的回调函数 console.log('p1_1') 作为一个微任务被推入微任务队列
  • 第二个 then() 中传入的回调函数 console.log('p1_2') 此时还没有被推入微任务队列,只有上一个 then() 中的 console.log('p1_1') 执行完毕后,console.log('p1_2') 才会被推入微任务队列

总结

  • Promise的then()链式调用,是通过then() 返回一个新的Promise实现的
  • 如果 Promise 的状态是 pending,那么 then 会在该 Promise 上注册一个回调,当其状态发生变化时,对应的回调将作为一个微任务被推入微任务队列
  • 如果 Promise 的状态已经是 fulfilled 或 rejected,那么 then() 会立即创建一个微任务,将传入的对应的回调推入微任务队列

3、Promise.resolve()与new Promise(resolve=>(resolve))的区别

EnquequeJob存放两种类型的任务, 即PromiseResolveThenableJob和PromiseReactionJob, 并且都是属于microtask类型的任务。

PromiseReactionJob: 可以通俗的理解为promise中的回调函数 PromiseResolveThenableJob(promiseToResolve, thenable, then): 创建和 promiseToResolve 关联的 resolve function 和 reject function。以 then 为调用函数,thenable 为this,resolve function和reject function 为参数调用返回。

参数为thenable对象

Promise中resolve一个thenable对象,执行步骤如何? 在 Promise 中 resolve 一个 thenable 对象时,

  • 需要先将 thenable 转化为 Promsie,
  • 然后立即调用 thenable 的 then 方法

这个过程需要作为一个 job 加入微任务队列,以保证对 then 方法的解析发生在其他上下文代码的解析之后

let thenable = {
    then(resolve, reject) {
        console.log('in thenable');
        resolve(100);
    }
};
var promiseB = new Promise((resolve) => {
    console.log('promiseB');
    resolve(thenable);
});
//或var promiseB = Promise.resolve(thenable);

var promiseA = new Promise((resolve) => {
    console.log('promiseA');
    resolve();
});

promiseB.then((res) => {
    console.log('out thenable ' + res)
}).then(() => {
    console.log('then2')
}).then(() => {
    console.log('then3')
});
promiseA.then(() => { 
    console.log(1);
}).then(() => { 
    console.log(2) 
}).then(() => { 
    console.log(3)
});

结果如下:

promiseB
promiseA
in thenable
1
out thenable 100
2
then2
3
then3

分析

in thenable 后于 promiseA 而先于 1 输出,同时out thenable 100 在 1 后输出。

  • 1、同步任务中,promiseB中先(输出promiseB)promiseB中resolve被调用,同时向microtask插入任务PromiseResolveThenableJob。promiseA中(输出promiseA),同时向microtask插入任务1
  • 2、在 PromiseResolveThenableJob 执行中会执行 thenable.then(),从而注册了另一个微任务out thenable。执行任务1(输出1),同时向microtask插入任务2。
  • 3、执行任务out thenable(输出out thenable 100),同时向microtask插入任务then2。执行任务2(输出2),同时向microtask插入任务3 。
  • 4、执行任务then2(输出then2),同时向microtask插入任务then3。执行任务3(输出3)。
  • 5、执行任务then3(输出then3),至此结束。

Promise.resolve(thenable)与new Promise(resolve=>(resolve(thenable)))的处理结果一致

正是由于规范中对 thenable 的处理需要在一个微任务中完成,从而导致了第一个 Promise 的后续回调被延后了1个时序

参数是一个 Promise 实例

  • 1、由于 Promise 实例是一个对象,其原型上有 then 方法,所以这也是一个 thenable 对象。
  • 2、同样的,浏览器会创建一个 PromiseResolveThenableJob 去处理这个 Promise 实例,这是一个微任务。
  • 3、在 PromiseResolveThenableJob 执行中,执行了 Promise.prototype.then,而这时 Promise 如果已经是 resolved 状态 ,then 的执行会再一次创建了一个微任务
var promiseA = Promise.resolve('promiseA');

var promiseB = new Promise((resolve) => {
    resolve(promiseA);
});
//或var promiseB = Promise.resolve(promiseA);
promiseB.then(() => { 
    console.log('then1') 
}).then(() => { 
    console.log('then2') 
}).then(() => { 
    console.log('then3') 
});

promiseA.then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
});

结果

//new Promise
1
2
then1
3
then2
then3
//采用或的结构Promise.resolve(promiseA)
then1
1
then2
2
then3
3

分析new Promise(resolve=>(resolve(promiseA)))情况

'then1'输出晚了两个时序。对于参数是一个promise ,resolve(promiseA)究竟如何工作的?

例子中,当promiseB被resolved的时候, 也就是将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,会向EnquequeJob插入一个PromiseResolveThenableJob任务。PromiseResolveThenableJob大概做了如下的事情:

() => { 
  promiseA.then(
    resolvePromiseB, 
    rejectPromiseB
  );
}

并且当resolvePromiseB执行后, promiseB的状态才变成resolve,也就是B追随A的状态。

  • 1、promiseA处于resolve状态,promiseB中resolve被调用,同时向microtask插入任务PromiseResolveThenableJob
  • 2、promiseA.then被调用, 向microtask插入任务1
  • 3、执行PromiseResolveThenableJob, 向microtask插入任务resolvePromise(如上面的promiseA.then(...))
  • 4、执行 任务1(即输出1),返回一个promise, 向microtask插入任务2
  • 5、执行resolvePromise, 此时promise终于变成resolve, 向microtask插入任务then1
  • 6、执行任务2(即输出2)
  • 7、执行任务then1(即输出then1),返回一个promise,向microtask插入任务then2
  • 8、执行任务then2(即输出then2),返回一个promise,向microtask插入任务then3,执行任务then3(即输出then3)

分析Promise.resolve(promiseA)情况

3、await/async函数和Promise怎么转换

async function async1() {
    console.log('async1')
    await async2();
    console.log('async1 end')
}
    
async function async2() {
    console.log('async2');
}
async1();
    
new Promise(function (resolve) {
    console.log('promise')
    resolve();
}).then(function () {
    console.log(1);
}).then(function () {
    console.log(2);
}).then(function () {
    console.log(3);
})

浏览器chrome76 环境中,结果如下:

//浏览器chrome76 
async1
async2
promise
async1 end
1
2
3

node环境中,async1 end被推迟了2个时序

//node v10.6.0,
async1
async2
promise
1
2
async1 end
3

以浏览器运行结果来转换

function async1(){
  console.log('async1 start');
  const p = async2();
  return Promise.resolve(p).then(() => {
      console.log('async1 end')
  });
}
    
function async2(){
  console.log('async2');
  return Promise.resolve();
}
//其他代码同上

总结

  • Promise.resolve(v) 不等于 new Promise(resolve => resolve(v)),因为如果 v 是一个 Promise 对象,前者会直接返回 v,而后者需要经过一系列的处理(主要是 PromiseResolveThenableJob)
  • 宏任务的优先级是高于微任务的

参考链接: 令人费解的 async/await 执行顺序

「译」更快的 async 函数和 promises