Promise 的then 里发生了什么
Promise 大家都知道怎么用, 但是对于内部的原理很多人都不是很清楚
来看一个面试题: Promise的then 是怎么实现的
首先来分析一下then
- then是属于实例上的方法
- 参数有2个,分别为onFulfilled, onRejected,并且都是可选的
- 可以实现链式调用
- then执行要执行Promise onFulfilled 或者 onRejected 方法
- 参数onFulfilled,onRejected 分别有自己的参数, 分别是resolve的参数跟reject的参数
- then只能使用前一个then的返回值
- then返回值不能是同一个promise
来一个一个看吧
- then是属于实例上的方法
Promise.prototype.then = function(){}
- 参数有2个,分别为onFulfilled, onRejected,并且都是可选的
Promise.prototype.then = function(onFulfilled,onRejected){}
- 可以实现链式调用
Promise.prototype.then = function(onFulfilled,onRejected){ return new Promise(function(resolve,reject){ // 代码省略 }) }
要实现promise链式 返回的必须是一个Promise 再由于status 改变状态了不能再变 所以需要第二个.... 第N个promise 调用新的resolve
- then执行要执行Promise onFulfilled 或者 onRejected 方法
Promise.prototype.then = function(onFulfilled,onRejected){ var self = this; // 保存当前上下文 return new Promise(function(resolve,reject){ if(self.status === 'resolved'){ onFulfilled(self.res) } if(self.status === 'rejected'){ onRejected(self.err) } }) }
Promise.resolve(res) 、、 同步代码的执行情况下 上述逻辑成立
- 参数onFulfilled,onRejected 分别有自己的参数, 分别是resolve的参数跟reject的参数
res 跟err 来源如下
function Promise(executor){ let self = this this.status = 'pending' // Promise 的状态值 this.res = undefined // 存成功之后的值 this.err = undefined // 存失败之后的值 executor(resolve,reject) // 省略代码 }
executor 有2个参数 分别为resolve,reject
当调用
new Promise((resolve,reject)=>{ setTimeout(()=>{ if(true){ resolve('res') }else{ reject('err') } },1000) })
executor 会执行 并且把成功的值或者失败的值抛出来,resolve跟reject也是2个函数 定义在Promise内部
function Promise(executor){ // ...代码省略 function resolve(res){ self.res = res } function reject(err){ self.err = err } executor(resolve,reject) }
resolve 跟reject 还需要改变 Promise的状态值 并且一旦发生改变之后不能再次改变
function Promise(executor){ // 代码省略 function resolve(res){ if(self.status === 'pending'){ self.status = 'resolved' self.res = res } } function reject(err){ if(self.status === 'pending'){ self.status = 'rejected' self.err = err } } executor(resolve,reject) }
我们在executor中操作的往往是异步代码, 这个之后直接then, status的状态值并未发生改变, 所以不会执行onFulfilled跟onRejected,
这个时候我们需要订阅
function Promise(executor){ // 代码省略 this.onFulfilledCallback = [] // 存成功之后的回调 this.onrejectedCallback = [] // 存失败之后的回调 function resolve(res){ if(self.status === 'pending'){ self.status = 'resolved' self.res = res } } function reject(err){ if(self.status === 'pending'){ self.status = 'rejected' self.err = err } } executor(resolve,reject) } new Promise(executor).then((onFulfilled,onrejected)=>{ var self = this; // 保存当前上下文 **注意: 第二次调用then this是指向new Promise的** return new Promise(function(resolve,reject){ // ...代码省略 if(self.status === 'pending'){ self.onFulfilledCallback.push(function(){ // 把成功之后需要做的事存起来 有多少个then就有多少个函数 onFulfilled(self.res) // 注意 这里的self.res 会随着then的调用发生改变 因为每次then都new 了一个Promise }) self.onrejectedCallback.push(function(){ // 把失败之后的事存起来 有多少个then就有多少个函数 onFulfilled(self.err) }) } }) })
当resolve的时候 或者reject的时候
一一拿出来执行
function resolve(res){ // 省略代码 self.res = res onFulfilledCallback.forEach(fn=>{ fn() }) }
实现链式调用我们需要对then函数执行的返回值做一个操作!!!
需求:
- then的参数onFulfilled函数 需要接收到上一个onfulfilled的执行结果
Promise.prototype.then = function(onFulfilled,onRejected){ return new Promise((resolve,reject)=>{ // ...代码省略 if(self.status === 'resolved'){ let x = onFulfilled(self.res) // 拿到onFulfilled的执行结果 注意:这里执行Promise.resolve() 同步代码 // 然后把x传递给下一个then resolve(x) } }) }
如果resolve是异步的话
Promise.prototype.then = function(onFulfilled,onRejected){ return new Promise((resolve,reject)=>{ // ...代码省略 if(self.status === 'resolved'){ let x = onFulfilled(self.res) // 拿到onFulfilled的执行结果 注意:这里执行的是Promise.resolve() 同步代码 // 然后把x传递给下一个then resolve(x) } if(self.status === 'pending'){ self.onFulfilledCallback.push(function(){ let x = onFulfilled(self.res) // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled} resolve(x) }) } }) } // 同时为了拿到 fn(){let x = onFulfilled ...} 得值 传递给下一个onFulfilled,onFulfilledCallback需要改写 onRejectedCallback同理 onFulfilledCallback.forEach(fn=>{ return fn() }) onRejectedCallback.forEach(fn=>{ return fn() })
-
- onFullfillled 的返回值可能是个promise 也可能是个普通值
考虑如下情况:
1. 返回值是个promise
Promsie.resolve(11).then(res=>{ return new Promise(executor) })
2. 返回值是个普通值
Promsie.resolve(11).then(res=>{ return 1 })
最关键的来了
我们需要对onFullfillled 的返回值进行判断
修改then函数
// ...代码省略 if(self.status === 'resolved'){ let x = onFulfilled(self.res) // 拿到onFulfilled的执行结果 注意:这里执行的是Promise.resolve() 同步代码 // 然后把x传递给下一个then promiseResolve(x,resolve,reject) }
function promiseResolve(x,resolve,reject){ if((typeof x != null && typeof x == 'object') || typeof x == 'function'){ // 确定是个引用类型 if (x instanceof Promise){ // 确定是个Promise 实例 let then = x.then then.call(x,res=>{ // 保证x调用res=>{}, res=>{}是then的onFulfilled方法 一直判断直到onFulfilled 返回的是个普通值 然后resolve出去 promiseResolve(res,resolve,reject) },err=>{ reject(err) }) } }else{ resolve(x) } }
- then返回值不能是同一个promise
考虑这样极端问题:
let p = new Promise(resolve=>{ resolve(3333) }) let p2 = p.then(resolve=>{ return p2 })
这样会出现什么问题呢, onFulfilled的返回结果如果是一个promise 上面的递归调用已经说明了 我们要不断的去递归最终的onFulfilled结果 然后再改变p2的status 这样变成了p2去等待p2的执行结果 函数死掉了
所以onFulfilled的返回结果需要跟then的返回结果去比较 修改函数
function promiseResolve(x,resolve,reject,promise){ if(x === promise ){ return new Error('引用错误!') } if((typeof x != null && typeof x == 'object') || typeof x == 'function'){ // 确定是个引用类型 if (x instanceof Promise){ // 确定是个Promise 实例 let then = x.then then.call(x,res=>{ // 保证x调用res=>{}, res=>{}是then的onFulfilled方法 一直判断直到onFulfilled 返回的是个普通值 然后resolve出去 promiseResolve(res,resolve,reject) },err=>{ reject(err) }) } }else{ resolve(x) } }
Promise.prototype.then = function(onFulfilled,onRejected){ // ...代码省略 let promise2 = new Promise((resolve,reject)=>{ if(self.status === 'pending'){ self.onFulfilledCallback.push(function(){ let x = onFulfilled(self.res) // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled} promiseResolve(x,resolve,reject,promise2) }) } }) return promise2 }
这段代码还是有错误, 由于javascript主线程是单线程的, 所以在then里的promiseResolve是拿不到promise2的 所以我们需要开启异步 使用定时器或者nextTick 这里我们用定时器
Promise.prototype.then = function(onFulfilled,onRejected){ // ...代码省略 let promise2 = new Promise((resolve,reject)=>{ if(self.status === 'pending'){ self.onFulfilledCallback.push(function(){ setTimeout(()=>{ let x = onFulfilled(self.res) // 这里的self.res 是上一个new Promise上的值 此时的onFUlfilled 相当于 fn(){let x = onFulfilled} promiseResolve(x,resolve,reject,promise2) },0) }) } }) return promise2 }
此时 整个then差不多完成了
需要补充的点
- onFulfilled,onRejected 有可能是undefined 这里未做判断
- 在new Promise 时 executor函数内部有可能报错 这里未使用try catch捕获
- 这里对于onRejected的错误未抛出
总结
then每次创建一个新的promise对象 对于同步的resolve,reject直接调用onFulfilled或者onRejected ,对于异步的resolve,reject使用
订阅发布模式,把每个resolve,reject 暂存起来 等达到条件时候一一执行, 并且拿到返回结果去跟内部的promise比较,并且判断如果是一个promise的话,不断解析onFulfilled 的返回结果 直至resolve出去