Promise 注册微任务和执行过程

第一段代码

 new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
  .then(() => {
    console.log("外部第一个then");
    return new Promise((resolve, reject) => {
      console.log("内部promise");
      resolve();
    })
    .then(() => {
    console.log("内部第一个then");
    })
    .then(() => {
    console.log("内部第二个then");
    });
  })
  .then(() => {
    console.log("外部第二个then");
  });

这个输出还是比较简单的,外部第一个 new Promise 执行,执行完 resolve ,然后执行外部第一个 then 。外部第一个 then 方法里面 return 一个 Promise,这个 return ,代表 外部的第二个 then 的执行需要等待 return 之后的结果。当然会先执行完内部两个 then 之后,再执行 外部的第二个 then ,机智如你,完全正确。

外部promise
外部第一个then
内部promise
内部第一个then
内部第二个then
外部第二个then

第二段代码

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
  .then(() => {
    console.log("外部第一个then");
    new Promise((resolve, reject) => {
      console.log("内部promise");
      resolve();
    })
      .then(() => {
        console.log("内部第一个then");
      })
      .then(() => {
        console.log("内部第二个then");
      });
  })
  .then(() => {
    console.log("外部第二个then");
  });
  

这段代码和第一段代码就相差一个 return ,然而结果确是不一样的。
那这个怎么理解呢?
我们核心要看 then 的回调函数是啥时候注册的,我们知道,事件机制是 “先注册先执行”,即数据结构中的 “队列” 的模式,first in first out。那么重点我们来看下他们谁先注册的。
外部的第二个 then 的注册,需要等待 外部的第一个 then 的同步代码执行完成。 当执行内部的 new Promise 的时候,然后碰到 resolve,resolve 执行完成,代表此时的该 Promise 状态已经扭转,之后开始内部的第一个 .then 的微任务的注册,此时同步执行完成。我们知道需要执行的动作是一个微任务,那么自然要先执行完同步任务,比如如下:

new Promise((resolve, reject) => {
    resolve();
    console.log(111);
})
.then(() => {
    consle.log(222);
})

这个代码显然优先输出执行 1111,再执行 222。因为 222 的输出是微任务的执行,111 是同步执行。
同理回到上面的代码,内部的 resolve 之后,当然是先执行内部的 new Promise 的第一个 then 的注册,这个 new Promise 执行完成,立即同步执行了后面的 .then 的注册。
然而这个内部的第二个 then 是需要第一个 then 的的执行完成来决定的,而第一个 then 的回调是没有执行,仅仅只是执行了同步的 .then 方法的注册,所以会进入等待状态。
这个时候,外部的第一个 then 的同步操作已经完成了,然后开始注册外部的第二个 then,此时外部的同步任务也都完成了。同步操作完成之后,那么开始执行微任务,我们发现 内部的第一个 then 是优先于外部的第二个 then 的注册,所以会执行完内部的第一个 then 之后,然后注册内部的第二个 then ,然后执行外部的第二个 then ,然后再执行内部的第二个 then。

外部promise
外部第一个then
内部promise
内部第一个then
外部第二个then
内部第二个then

我们发现,这里显然是执行完一个 then,接着会注册该 then 之后的下一个 then,按照任务队列的原理,我们可以发现,内外 then 是交替执行,然后交替注册的。所以才会出现输出内外交替内容。
另外,我这里所说的 then 的注册,是指微任务队列的注册,并不是 .then 的方法的执行,实际上 .then 方法的执行,我们可以理解为仅仅只是初始化而已。如果看过源码的会知道,.then 的执行确实是同步的,内部是再开启一个 new Promise ,但是由于上一个状态未流转,该 then 并不会此时注册到微任务队列中,而是会等待上一个的执行完成,所以我们把 .then 没注册微任务就理解成尚没执行是没有问题的。

第三段代码

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
  .then(() => {
    console.log("外部第一个then");
    let p = new Promise((resolve, reject) => {
      console.log("内部promise");
      resolve();
    })
    p.then(() => {
        console.log("内部第一个then");
      })
    p.then(() => {
        console.log("内部第二个then");
      });
  })
  .then(() => {
    console.log("外部第二个then");
  });

这段代码的差异,就是内部的 Promise 的代码的写法变了,不再是链式调用。
这里怎么理解呢?
这里在执行内部的 new Promise 的 resolve 执行完成之后(扭转了该 Promise 的状态),new Promise 之后的两个同步 p.then 是两个执行代码语句,都是同步执行,自然是会同步注册完。
两种方式的最主要的区别是:
•链式调用的注册是前后依赖的 比如上面的外部的第二个 then 的注册,是需要外部的第一个的 then 的执行完成。•变量定义的方式,注册都是同步的 比如这里的 p.then 和 var p = new Promise 都是同步执行的。
所以这里的代码执行就比较清晰了,内部都执行完成之后(因为都优先于外部的第二个 then 的注册),再执行外部的第二个 then

外部promise
外部第一个then
内部promise
内部第一个then
内部第二个then
外部第二个then

第四段代码

let p = new Promise((resolve, reject) => {
console.log("外部promise");
resolve();
})
p.then(() => {
  console.log("外部第一个then");
  new Promise((resolve, reject) => {
    console.log("内部promise");
    resolve();
  })
    .then(() => {
      console.log("内部第一个then");
    })
    .then(() => {
      console.log("内部第二个then");
    });
})
p.then(() => {
  console.log("外部第二个then");
});

这段代码中,外部的注册采用了非链式调用的写法,根据上面的讲解,我们知道了外部代码的 p.then 是并列同步注册的。所以代码在内部的 new Promise 执行完,p.then 就都同步注册完了。
内部的第一个 then 注册之后,就开始执行外部的第二个 then 了(外部的第二个 then 和 外部的第一个 then 都是同步注册完了)。然后再依次执行内部的第一个 then,内部的第二个 then。

第五段代码

核心思想:
Promise 的 then 的 注册微任务队列 和 执行 是分离的。注册 : 是完全遵循 JS 和 Promise 的代码的执行过程。执行 : 先 同步,再 微任务 ,再 宏观任务。

new Promise((resolve, reject) => {
 console.log("外部promise");
 resolve();
})
 .then(() => {
   console.log("外部第一个then");
   new Promise((resolve, reject) => {
     console.log("内部promise");
     resolve();
   })
     .then(() => {
       console.log("内部第一个then");
     })
     .then(() => {
       console.log("内部第二个then");
     });
   return new Promise((resolve, reject) => {
     console.log("内部promise2");
     resolve();
   })
     .then(() => {
       console.log("内部第一个then2");
     })
     .then(() => {
       console.log("内部第二个then2");
     });
 })
 .then(() => {
   console.log("外部第二个then");
 });


在这里插入图片描述

这段代码,其实就是结合了第一道题目和第二道题目综合而成。外部的第二个 then,依赖于内部的 return 的执行结果,所以会等待 return 执行完成。内部的第一段 new Promise 变成和内部的第二段 new Promise 的交替输出了,理解方式和第二段代码一样。

外部promise
外部第一个then
内部promise
内部promise2
内部第一个then
内部第一个then2
内部第二个then
内部第二个then2
外部第二个then

Promise/A+ 和 webkit 内核 JS 引擎的 Promise 的实现差异
我们知道 ES6 的 Promise 是需要考虑向下兼容的,开发当中往往没有用系统内核的 Promise,而是使用 npm install promise 来引入的。那么 promise 的 js 实现和浏览器的实现是完全一致的吗?

按照上面的四段代码的解析,我们理解到了,Promise 的 then 的执行,是依赖于上一个 then 的执行完成之后,即 resolve 状态之后,才开始注册到微任务队列中的。

我们一起看一道题目,这里的区别是 then 返回了一个 Promise.resolve();

new Promise((resolve, reject) => {
console.log(‘外部promise’);
resolve();
})
.then(() => {
console.log(‘外部第一个then’);
new Promise((resolve, reject) => {
console.log(‘内部promise’);
resolve();
})
.then(() => {
console.log(‘内部第一个then’);
return Promise.resolve();
})
.then(() => {
console.log(‘内部第二个then’);
})
})
.then(() => {
console.log(‘外部第二个then’);
})
.then(() => {
console.log(‘外部第三个then’);
})
我们先忽略内部第一个 then 的 return ,按照上面所学习到的,正常理解,我们能得出依然是内外交替注册和运行。

output:

外部promise
外部第一个then
内部promise
内部第一个then
外部第二个then
内部第二个then
外部第三个then
此题执行顺序图:

promise
上面我们是使用 Promise 的 js 实现的代码输出的结果。
然而你把这段代码放在 chrome/safari 上跑一下,发现结果不一样,如下是 webkit 内核浏览器跑出来的结果。
promise
这个是什么原因呢?为啥多了一个 return Promise.resolve(),就把外层的 then 都执行完了呢?要理解这个,我们还是要从注册和执行来区分理解。
在执行输出 “内部第一个 then ”之后,碰到 return Promise.resolve();我们就来分析这个 Promise.resolve();
Promise/A+ 的实现
执行 return Promise.resolve() ,创建一个 Promise 实例,将 Promise 实例设置为 resolve 状态,这个 Promise.resolve() 是同步的,且该 Promise 已经完成了,所以他并不会影响到其他 then 的注册。所以上述我们分析是完全正确的。如下是 Promise.resolve 的实现,我们发现,完全是同步的,所以不影响最终结果。

Promise.resolve = function (value) {
  if (value instanceof Promise) return value;
  if (value === null) return NULL;
  if (value === undefined) return UNDEFINED;
  if (value === true) return TRUE;
  if (value === false) return FALSE;
  if (value === 0) return ZERO;
  if (value === '') return EMPTYSTRING;
  if (typeof value === 'object' || typeof value === 'function') {
    try {
      var then = value.then;
      if (typeof then === 'function') {
        return new Promise(then.bind(value));
      }
    } catch (ex) {
      return new Promise(function (resolve, reject) {
        reject(ex);
      });
    }
  }
  return valuePromise(value);
};

Promise 的 浏览器(webkit)的实现
执行 return Promise.resolve() ,创建一个 Promise 实例,执行 resolve ,此时将该 Promise 的 resolve 的 value(这里是undefined) 进入微任务队列,将该 Promise 的状态扭转为 resolve。然后接着执行了之前注册好的 “外部第二个then”,然后注册 “外部第三个then” ,接着执行 “内部第一个then” 的 return 的 resolve 的这个 undefined value 的 Promise,执行完成之后,然后注册下一个then,但是没有下一个 then 了,执行完成,整个 return 任务完成,本次同步任务也执行完成,接着执行注册的 “外部第三个then” ,执行完成之后,注册 “外部第四个then”,此时 ”内部第一个then“ 执行完成,注册 ”内部第二个then”,最后执行完“外部第四个then”,再执行 刚刚注册的“内部第二个then”.

源代码如下:

void Promise::Resolver::Resolve(Handle<Value> value) {
  i::Handle<i::JSObject> promise = Utils::OpenHandle(this);
  i::Isolate* isolate = promise->GetIsolate();
  LOG_API(isolate, "Promise::Resolver::Resolve");
  ENTER_V8(isolate);
  EXCEPTION_PREAMBLE(isolate);
  i::Handle<i::Object> argv[] = { promise, Utils::OpenHandle(*value) };
  has_pending_exception = i::Execution::Call(
      isolate,
      isolate->promise_resolve(),
      isolate->factory()->undefined_value(),
      arraysize(argv), argv,
      false).is_null();
  EXCEPTION_BAILOUT_CHECK(isolate, /* void */ ;);
}
PromiseResolve = function PromiseResolve(promise, x) {
    PromiseDone(promise, +1, x, promiseOnResolve)
}
function PromiseDone(promise, status, value, promiseQueue) {
    if (GET_PRIVATE(promise, promiseStatus) === 0) {
        PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status);
        PromiseSet(promise, status, value);
    }
}
有了上面的理解之后,如果外层再加一个 then ,那么也知道结果了,执行完刚刚注册的 “内部第二个then”,之后,开始执行注册的 “外部第五个then”。

promise

巩固一下
结合上面已经学会的 Promise 的执行顺序,你应该能答出如下这道题的答案了吧,如果还不会,可以考虑再仔细看一遍。

new Promise((resolve, reject) => {
  console.log("外部promise");
  resolve();
})
.then(() => {
    console.log("外部第一个then");
    new Promise((resolve, reject) => {
        console.log("内部promise");
        resolve();
    })
    .then(() => {
        console.log("内部第一个then");
    })
    .then(() => {
        console.log("内部第二个then");
    });
    return new Promise((resolve, reject) => {
        console.log("内部promise2");
        resolve();
    })
    .then(() => {
        console.log("内部第一个then2");
    })
    .then(() => {
        console.log("内部第二个then2");
    });
})
.then(() => {
    console.log("外部第二个then");
});
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值