参考:深度揭秘 Promise 微任务注册和执行过程

第一段代码

  1. new Promise((resolve, reject) => {
  2. console.log("外部promise");
  3. resolve();
  4. })
  5. .then(() => {
  6. console.log("外部第一个then");
  7. return new Promise((resolve, reject) => {
  8. console.log("内部promise");
  9. resolve();
  10. })
  11. .then(() => {
  12. console.log("内部第一个then");
  13. })
  14. .then(() => {
  15. console.log("内部第二个then");
  16. });
  17. })
  18. .then(() => {
  19. console.log("外部第二个then");
  20. });

output:

  1. 外部promise
  2. 外部第一个then
  3. 内部promise
  4. 内部第一个then
  5. 内部第二个then
  6. 外部第二个then

这里需要注意的是,外部第一个then后面是一个return语句,外部第二个then是依赖于这个return的结果的,所以第二个then是会等这个return 后面执行完出结果才会执行;所以最后才打印外部第二个then

第二段代码

  1. new Promise((resolve, reject) => {
  2. console.log("外部promise");
  3. resolve();
  4. })
  5. .then(() => {
  6. console.log("外部第一个then");
  7. new Promise((resolve, reject) => {
  8. console.log("内部promise");
  9. resolve();
  10. })
  11. .then(() => {
  12. console.log("内部第一个then");
  13. })
  14. .then(() => {
  15. console.log("内部第二个then");
  16. });
  17. })
  18. .then(() => {
  19. console.log("外部第二个then");
  20. });

output:

  1. 外部promise
  2. 外部第一个then
  3. 内部promise
  4. 内部第一个then
  5. 外部第二个then
  6. 内部第二个then

这里可能让人很不解的地方在于,为什么 内部第一个then会在外部第二个then前面?
我们都知道,事件机制是“先注册先执行”,即数据结构中的队列模式。所以要解释这个问题需要看看谁先注册的。
外部的第二个then的注册,需要等待外部第一个then内的同步代码执行完成
image.png
如图所示,代码到resolve这都是同步的(包括resolve方法也是同步的);重点来了:resolve执行完成,代表此时的改promise状态已经扭转,之后开始内部的第一个.then的微任务注册,此时同步执行完成,外部第二个then才注册;所以内部第一个then早于外部第二个then注册;所以内部then第一个then先执行(此时也完成了内部第二个then的注册),外部第二个then紧接着执行,最后执行内部第二个then.

第三段代码

  1. new Promise((resolve, reject) => {
  2. console.log("外部promise");
  3. resolve();
  4. })
  5. .then(() => {
  6. console.log("外部第一个then");
  7. let p = new Promise((resolve, reject) => {
  8. console.log("内部promise");
  9. resolve();
  10. })
  11. p.then(() => {
  12. console.log("内部第一个then");
  13. })
  14. p.then(() => {
  15. console.log("内部第二个then");
  16. });
  17. })
  18. .then(() => {
  19. console.log("外部第二个then");
  20. });

output:

  1. 外部promise
  2. 外部第一个then
  3. 内部promise
  4. 内部第一个then
  5. 内部第二个then
  6. 外部第二个then

这段代码的差异,就是内部的Promise的代码不再是链式调用。
两个p.then是同步代码,所以先注册 内部第一个then、紧接着 注册 内部第二个 then;最后同步代码执行完 注册 外部第二个then,此时任务队列中有三个任务,再按照先进先出的原则依次执行这三个任务。

第四段代码

  1. let p = new Promise((resolve, reject) => {
  2. console.log("外部promise");
  3. resolve();
  4. })
  5. p.then(() => {
  6. console.log("外部第一个then");
  7. new Promise((resolve, reject) => {
  8. console.log("内部promise");
  9. resolve();
  10. })
  11. .then(() => {
  12. console.log("内部第一个then");
  13. })
  14. .then(() => {
  15. console.log("内部第二个then");
  16. });
  17. })
  18. p.then(() => {
  19. console.log("外部第二个then");
  20. });

output:

  1. 外部promise
  2. 外部第一个then
  3. 内部promise
  4. 外部第二个then
  5. 内部第一个then
  6. 内部第二个then

这块同理,两个p.then是同步注册的,外部第一个then内执行到resolve方法时才开始注册内部第一个then,所以外部第二个then先打印,再打印内部第一个then

总结

  • promise的链式调用两个then,第二个then的注册需要等第一个then内的同步代码执行完成(注意:resolve方法也算同步方法,只是后面的then是异步的)。并且微任务队列是先注册先执行。
  • 如果第一个then内有return,第二个then会等return 语句执行完才开始注册