Promise.then原理

257 阅读7分钟

下面的内容建议了解事件循环后再看效果更好

开胃小知识点(不懂没关系,往下看):
new Promise((resolve, reject) => {
    console.log("fun2")
}).then((data)=>{
    console.log(data)
    console.log("await堵塞")
})
// 结果为:fun2

因为一直没有改变状态所以外层new Promise一直是padding状态,所以then不会执行,new Promise内不会自动resolve,而then方法可以返回一个  的 Promise 实例,注意是 新的实例,与 Promise 链条传递的 之前的 Promise 实例 并无多大关系,而且这个新的实例 状态 一定不是 pending,这是then 可以链式调用的原因。如下代码

new Promise((resolve, reject) => {
    console.log("fun2")
    resolve(1)
}).then((data)=>{
    console.log(data)
    console.log("await堵塞")
}).then(()=>{
    console.log('then')
})
// 结果为:fun2,1,await堵塞,then

Promise.then

then的返回值

首先,then 可以链式调用的原因是因为 then 方法默认会返回一个  的 Promise 实例,注意是 新的实例,与 Promise 链条传递的 之前的 Promise 实例 并无多大关系,而且这个新的实例 状态 一定不是 pending

then返回值分类
  1. return值(等同于return undefined)/显示地returnpromise的值
  2. 显示 return 具有 resolve 的 promise 的值
then对不同类型返回值的处理
  1. 处理return 非promise的值
Promise.resolve().then(() => {});

上面代码then中相当于return undefined,在被抛出返回值的时候,会被Promise.resolve包装,所以最后的promise状态为fulfilled,这个包装的过程不发生在return时候,而发生在整体代码执行完 抛出 返回值的过程 被包装。也就是说你 不能理解 成这样:

Promise.resolve().then(() => { return Promise.resolve(undefined) }) 
  1. 处理return具有resolve的promise的值
Promise1.resolve().then(() => Promise2.resolve())

这里的过程很复杂,分为三步:

  • 执行 ResolvePromise (来源于 v8 引擎内部),调用 promiseResolveThenableJobTask (来源于 v8 引擎内部) 产生一个微任务放置到微任务队列,这个微任务就是一个函数
let promiseResolveThenableJobTask = () => {p1.then((value) => { ReslovePromise(p2, value) // 传递 promise
})} 
  • 在主线程任务清空的情况下,取出并执行 promiseResolveThenableJobTask 微任务,执行完这个任务也就是调用这个函数,会产生一个 Promise2.fulfilled 的微任务,这个微任务作用是将内部的 Promise2 的结果传递给 then 外部
  • 主线程从微任务队列取出执行 Promise2.fulfilled 任务,调用 ReslovePromise 函数 ,然后用 Promise.resolve 包装返回结果

所以,按照执行的顺序,在时间顺序上完全可以理解为插入了两个 你感受不到的微任务,1.promiseResolveThenableJobTask微任务;2.Promise2.fulfilled微任务(Promise2指的是里面的promise实例)

接下来我们来看一道经典例题

Promise.resolve()              // p1 
.then(() => {                  
    console.log(0);            // then0
    return Promise.resolve();  // p0
}).then(() => {
    console.log(4);            // then4
});
Promise.resolve()              // p2
.then(() => { 
    console.log(1);            // then1
}).then(() => {
    console.log(2);            // then2
}).then(() => {
    console.log(3);            // then3
}).then(() => {
    console.log(5);            // then5
}).then(() => {
    console.log(6);            // then6
});// 0 1 2 3 4 5 6 

如代码中注释中所说注释return这一行,相当于第一种:return非promise的返回值;2.不注释这一行,相当于第二种:return具有resolve的promise的返回值

注释情况我们不解释:结果一定是交替执行,不懂的小伙伴可以看我的上一篇文章nodejs事件循环

我们根据上面学习的知识来解释一下不注释return行时,返回具有resolve的promise返回值的执行即上面代码的执行顺序。

  1. 执行完所有同步代码, p1 和 p2 的状态变为 fulfilled,并且微任务队列中插入两个处理后续的then的微任务(放入微任务队列的是then里面的回调,nodejs事件循环 有提过),微任务队列变成 【then0,then1】(then根据里面的打印值命名)
  2. 从微任务中取出 then0输出0,看到返回值是promise类型,调用NewPromiseResolveThenableJobTask产生一个微任务,其实这个微任务我们可以理解为在Promise.resolve()中自动加了一个then,then就是微任务,此时微任务队列变成  【then1,NewPromiseResolveThenableJobTask(p1.fulfilled)】
  3. 从微任务队列中取出 then1 并执行,输出 1,调用下面的then2,微任务队列变成  【NewPromiseResolveThenableJobTask(p1.fulfilled),then2】
  4. 从微任务队列中取出 NewPromiseResolveThenableJobTask(p1.fulfilled) 并执行,然后向微任务队列添加微任务p0.fulfilled(p0指里面返回的Promise.resolve())这里就是传值到外层,这是一个异步任务(作用:将 p0 传递到then外部),微任务队列变为 【then2, p0.fulfilled】
  5. 从微任务队列中取出 then2 并执行,输出 2,将下一个then3放入微任务队列 ,微任务队列变成 [p0.fulfilled, then3]
  6. 从微任务队列中取出 p0.fulfilled 并执行,将 p0 传递给传出去,将then4放入微任务队列,微任务队列变成 【then3,then4】
  7. 从微任务队列中取出 then3 并执行,输出 3,将下一个then5放入微任务队列,微任务队列变成 【then4,then5】
  8. 从微任务队列中取出 then4 并执行,输出 4,微任务队列变成 【then5】
  9. 从微任务队列中取出 then5 并执行,输出 5,将下一个then6放入微任务队列,微任务队列变成 【then6】
  10. 从微任务队列中取出 then6 并执行,输出 6,执行完毕

如果你不想了解的那么细致,你直接将代码按照时间顺序将 return promise 替换为两个 then,因为这些时间执行了两个处理 promise 的 微任务

替换代码如下

Promise.resolve()              // p1 
.then(() => {                  
    console.log(0);            // then0
    return Promise.resolve()
    .then(() => {
        console.log('隐藏');            // 这里是隐藏的第一个微任--then
    });                                 // 隐藏的第二个微任务--向外传值
}).then(() => {
    console.log(4);            // then4
});
Promise.resolve()              // p2
.then(() => { 
    console.log(1);            // then1
}).then(() => {
    console.log(2);            // then2
}).then(() => {
    console.log(3);            // then3
}).then(() => {
    console.log(5);            // then5
}).then(() => {
    console.log(6);            // then6
});// 0 1 2 3 4 5 6 

变形

如果我们将 promise 链条后面的 then 拿进来呢?

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve().then(() => {
        console.log(4);
    }).then(() => {
        console.log(7);
    }).then(() => {
        console.log(8);
    });
});
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6);
});// 0 1 4 2 7 3 8 5 6 

其实这个代码用我们上一篇文章nodejs事件循环就可以解释了,将Promise.resolve()作为同步代码,你会看到交替执行的结果.其实它是执行完内部的 promise 链条后,才会执行 promiseResolveThenableJobTask 后面的过程

总结

promise.then 显示指定类型为 promise 作为返回值,会引起 js 解析引擎 额外的处理过程,多了两个时序,可以将这两个时序理解为,加了一个then和向外传值,这样理解让这个过程变得更加简单,当然如果then中返回promise实例,且promise实例后面又跟一个then,则就多一个实例,即向外传递值的过程。

思考(不懂可忽略,几乎不会有这样的代码)

我们思考一个问题,如果中间有几个then没有呢?我说的是new promise嵌套的情况,代码如下:

// 实验代码
new Promise(res => {//8
    res(new Promise(res => {//7
        res(new Promise(res => {//6
            res(new Promise(res => {//5
                res(new Promise(res => {//4
                    res(new Promise(res => {//3
                        res(new Promise(res => {//2
                            res(new Promise(res => { //1
                                res('fun2 微任务')
                            }).then((data) => {
                                console.log(data, 'then1')
                                return data
                            })
                            )
                        }).then((data) => {
                            console.log(data, 'then2')
                            return data
                        })
                        )
                    }).then((data) => {
                        console.log(data, 'then3')
                        return data
                    })
                    )
                })
                // .then((data) => {
                //     console.log(data, 444)
                //     return data
                // })
                )
            })
                // .then((data)=>{
                //     console.log(data, 555)
                //     return data
                // })
            )
        })
            .then((data) => {
                console.log(data, 666)
                return data
            })
        )
    })
        .then((data) => {
            console.log(data, 777)
            return data
        })
    )
})
    
// 参考代码
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(4);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6);
}).then(() => {
    console.log(7);
}).then(() => {
    console.log(8);
}).then(() => {
    console.log(9);
}).then(() => {
    console.log(10);
}).then(() => {
    console.log(11);
}).then(() => {
    console.log(12);
})

自己总结的结论(仅供参考)

上面的实验代码

  1. 如果每个then都没有缺失,“fun2 微任务 666”后面应该是“11”
  2. 如果缺失了“fun2 微任务 555”,则“fun2 微任务 666”后面应该是“10”
  3. 如果缺失了“fun2 微任务 555”和“fun2 微任务 444”,则“fun2 微任务 666”后面应该是“9”
  4. 如果缺失了“fun2 微任务 555”和“fun2 微任务 444”和“fun2 微任务 333”,则“fun2 微任务 666”后面应该是“8”
  • 我查微任务的方法是,先将所有的都加then,再各自加一个向外传递值的微任务,如果是连续多个then没有的情况,向外传值的微任务合并为一个

参考文章:
blog.csdn.net/qq_53225741…