手写 Promise 及相关代码理解

发布于 2023-05-20 15:25:34 字数 22902 浏览 45 评论 0

Promise 在日常开发中经常使用,但内部原理不太 理解,网上常看见一些源码的解析,得空了,便想看看相关的实现,对于之后的开发必然是有帮助的。

该理解为阅读他人博客之后的精简部分,用于本人理解,不一定适用于初学者。

1. 分析

1.1 极精简版

首先,我们看下 Promise 是如何使用的

new Promise((resolve, reject) => {
    resolve('value')
})
.then(res => {
    // 处理 resolve
}, err => {
    // 处理 reject
})

我们知道 Promise 有三个状态,默认为 pending, 还有 resolvedrejected.

由以上使用方式可知,Promise 是一个可以被 new 进行实例化的类,该类初始化接受一个函数,函数具有两个参数,一个函数将 Promise 状态变为 resolved, 一个将状态转变为 rejected

实例之后具有 .then 方法,该方法同样可以接受两个参数,分别处理resolvedrejected 之后的 Promise。再依据 Promises/A+ 规范, ,我们可以写出以下代码:

// 常用字符串用变量代替
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

class PPromise {
  constructor(executor){
    this.state = PENDING;
    this.value = null;
    this.error = null;
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    executor(this.resolve,this.reject)
  }
  resolve(value) {
    if(this.state === PENDING) {
      this.state = RESOLVED;
      this.value = value;
    }
  }
  reject(error) {
    if(this.state === PENDING) {
      this.state = REJECTED;
      this.error = error;
    }
  }
  then(onFulFilled, onRejected) {
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => {
        throw r
    }
    if (this.state === RESOLVED) {
        onFulFilled(this.value)
    }
    if (this.state === REJECTED) {
        onRejected(this.error)
    }
  }
}

// 测试代码
new PPromise((resolve, reject) => {
  resolve(456)
})
.then(res => {
  console.log(res)
},err => {
  console.log(err)
})

以上代码,即简单的实现了 Promise 的功能。

但是,经常我们在 Promise 的构造函数中,并非是立即进行 resolve 或者 reject, 而是存在异步 resolve 的情况,于是我们继续改进.

1.2 支持异步

其实支持异步的逻辑也很简单,即当 .then 执行的时候,Promise 的状态还是 pending

这时候,不能立即执行 onFulFilled或者onRejected方法,那我们可以将这两个函数保存起来,利用回调,在真正 resolve 的时候执行这两个函数:

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

class PPromise {
  constructor(executor){
    this.state = PENDING;
    this.value = null;
    this.error = null;
    // 两个数组存放对应的回调
    this.onResolvedCallback = []
    this.onRejectedCallback = []
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    executor(this.resolve,this.reject)
  }
  resolve(value) {
    if(this.state === PENDING) {
      this.state = RESOLVED;
      this.value = value;
      // 执行每个回调
      this.onResolvedCallback.forEach(fun => {
        fun(this.value)
      })
    }
  }
  reject(error) {
    if(this.state === PENDING) {
      this.state = REJECTED;
      this.error = error;
      // 执行每个回调
      this.onRejectedCallback.forEach(fun => {
        fun(this.error)
      })
    }
  }
  then(onFulFilled, onRejected) {
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => {
        throw r
    }
    if (this.state === RESOLVED) {
        onFulFilled(this.value)
    }
    if (this.state === REJECTED) {
        onRejected(this.error)
    }

    if (this.state === PENDING) {
        // 存回调函数
        this.onResolvedCallback.push(onFulFilled)
        this.onRejectedCallback.push(onRejected)
    }
  }
}

// 测试代码
new PPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(456)
  }, 200);
})
.then(res => {
  console.log(res)
},err => {
  console.log(err)
})

此时即能兼容异步的情况

1.3 .then方法异步

我们看下如下的例子:

console.log(1)
new Promise((resolve,rejected) => {
    console.log(2)
    resolve(4)
})
.then(res => {
    console.log(res)
})
console.log(3)

我们知道以上的代码会输出 1,2,3,4,而我们目前的代码则会输出1,2,4,3

即 .then 方法是异步而非同步执行,因此3会在4之前打印,因此我们可以这样处理,将 resolvereject 代码变为异步的:

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

class PPromise {
  constructor(executor){
    this.state = PENDING;
    this.value = null;
    this.error = null;
    this.onResolvedCallback = []
    this.onRejectedCallback = []
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    executor(this.resolve,this.reject)
  }
  resolve(value) {
    // 异步执行
    setTimeout(() => {
      if(this.state === PENDING) {
        this.state = RESOLVED;
        this.value = value;
        this.onResolvedCallback.forEach(fun => {
          fun(this.value)
        })
      }
    }, 0);
  }
  reject(error) {
    // 异步执行
    setTimeout(() => {
      if(this.state === PENDING) {
        this.state = REJECTED;
        this.error = error;
        this.onRejectedCallback.forEach(fun => {
          fun(this.error)
        })
      }
    }, 0);
  }
  then(onFulFilled, onRejected) {
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => {
        throw r
    }
    if (this.state === RESOLVED) {
        onFulFilled(this.value)
    }
    if (this.state === REJECTED) {
        onRejected(this.error)
    }

    if (this.state === PENDING) {
        // 存回调函数
        this.onResolvedCallback.push(onFulFilled)
        this.onRejectedCallback.push(onRejected)
    }
  }
}

// 测试代码
console.log(1)
new PPromise((resolve,rejected) => {
    console.log(2)
    resolve(4)
})
.then(res => {
    console.log(res)
})
console.log(3)

1.4 链式调用

我们知道 Promise 中 .then 出的结果还可以继续调用 .then 方法,Promises/A+ 规范规定 Promise.then 必须返回一个 Promise 对象,因此改写 then 方法:

then(onFulFilled, onRejected) {
// 返回一个新的 Promise
    return new PPromise((resolve, reject) => {
        onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
        onRejected = typeof onRejected === 'function' ? onRejected : r => {
            throw r
        }
        if (this.state === RESOLVED) {
            onFulFilled(this.value)
        }
        if (this.state === REJECTED) {
            onRejected(this.error)
        }

        if (this.state === PENDING) {
            this.onResolvedCallback.push(onFulFilled)
            this.onRejectedCallback.push(onRejected)
        }
    }
})

由于 Promise 的构造函数会立即执行,因此不影响原有的代码

  1. .then 返回普通值

考虑以下的例子:

new Promise((resolve,reject) => {
    resolve(1)
})
.then(res => {
    return res + '2'
})
.then(res => {
    console.log(res)  // 打印 12
})

new Promise((resolve,reject) => {
    resolve(1)
})
.then(res => {
    console.log(res)  // 打印 1
})
.then(res => {
    console.log(res)   // 打印 undefined
})

即链式调用中,下一个的 then 会接收上一个 thenreturn 的结果,如果没有 return, 则接收 undefined

  1. .then 返回Promise
new Promise((resolve,reject) => {
    resolve(1)
})
.then(res => {
    return new Promise((resolve, reject) => {
        resolve(2)
    })
})
.then(res => {
    console.log(res);   // 打印2
})

即在链式调用中,如果上一个 then 返回 Promise,那么下一个 then 会接收上一个 Promiseresolve或者reject的值。

再次进行完善:

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

const Promises = [];

class PPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = null;
    this.error = null;
    this.onResolvedCallback = []
    this.onRejectedCallback = []
    this.resolve = this.resolve.bind(this);
    this.reject = this.reject.bind(this);
    executor(this.resolve, this.reject)
  }
  resolve(value) {
    setTimeout(() => {
      if (this.state === PENDING) {
        this.state = RESOLVED;
        this.value = value;
        this.onResolvedCallback.forEach(fun => {
          fun(this.value)
        })
      }
    }, 0);
  }
  reject(error) {
    setTimeout(() => {
      if (this.state === PENDING) {
        this.state = REJECTED;
        this.error = error;
        this.onRejectedCallback.forEach(fun => {
          fun(this.error)
        })
      }
    }, 0);
  }
  then(onFulFilled, onRejected) {
    const newPromise = new PPromise((resolve, reject) => {
      onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : v => v
      onRejected = typeof onRejected === 'function' ? onRejected : r => {
        throw r
      }
      if (this.state === RESOLVED) {
        try {
          const returnValue = onFulFilled(this.value)
          this.resolutionProcedure(newPromise,returnValue,resolve,reject)
        } catch (error) {
          reject(error)
        }
      }
      if (this.state === REJECTED) {
        try {
          const returnValue = onRejected(this.error)  
          this.resolutionProcedure(newPromise,returnValue,resolve,reject)
        } catch (error) {
          reject(error)
        }
      }

      if (this.state === PENDING) {
        this.onResolvedCallback.push(() => {
          try {
            const returnValue = onFulFilled(this.value)
            this.resolutionProcedure(newPromise,returnValue,resolve,reject)
          } catch (error) {
            reject(error)
          }
        })
        this.onRejectedCallback.push(() => {
          try {
            const returnValue = onRejected(this.error)
            this.resolutionProcedure(newPromise,returnValue,resolve,reject)
          } catch (error) {
            reject(error)
          } 
        })
      }
    })
    Promises.push(newPromise)
    return newPromise;
  }
  resolutionProcedure(promise,x,resolve,reject) {
    // 防止循环引用
    if(x === promise) {
      return reject(new TypeError('Error'))
    }

    // 如果 x 是 thenable 对象, 那么将 x.then 的结果,再进行处理
    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
      try {
        then = x.then
        if (typeof then === 'function') {
          then.call(x, function rs(y) {
            if (!isCalled){
              isCalled = true;
              this.resolutionProcedure(promise, y, resolve, reject)
            }
          }, function rj(r) {
            if(!isCalled) {
              isCalled = true;
              reject(r)
            }
          })
        } else {
          resolve(x)
        }
      } catch(e) {
        if(!isCalled) {
          isCalled = true;
          reject(e)
        }
      }
    } else {
      resolve(x)
    }
  }
}

// 测试代码
new PPromise((resolve,reject) => {
  resolve(1)
})
.then(res => {
  console.log('res1',res)   // res1 1
  return new PPromise((resolve, reject) => {
      resolve(2)
  })
})
.then(res => {
  console.log('res2',res);  // res2 2
})

至此,一个比较完整的 Promise 就完成.

之后,我们可以添加一些常见的类方法和实例方法

1.5 .catch 方法

.catch 方法接受一个函数作为参数,该函数会在之前的 Promise reject 时执行。

class PPromise {
    ...
    catch(onRejected){
        return this.then(null, onRejected)
    }
}

// 测试代码
new PPromise((resolve,reject) => {
  resolve(1)
})
.then(res => {
  console.log('res1',res)
  return new PPromise((resolve, reject) => {
    reject(2)
  })
})
.then(res => {
  console.log('res2',res);
})
.catch(err => {
  console.log(err)
})

1.6 Promise.resolve()

该方法接收一个值作为参数,返回 resolve 该值的 Promise

PPromise.resolve = (value) => {
  return new PPromise((resolve, reject) => {
    resolve(value)
  })
}

// 测试代码
new PPromise((resolve,reject) => {
  resolve(1)
})
.then(res => {
  console.log('res1',res)
  return PPromise.resolve(2)
})
.then(res => {
  console.log('res2',res);   // 2
})

1.7 Promise.reject()

同 Promise.resolve(),代码如下:

PPromise.reject = (value) => {
  return new PPromise((resolve, reject) => {
    reject(value)
  })
}

// 测试代码
new PPromise((resolve,reject) => {
  resolve(1)
})
.then(res => {
  console.log('res1',res)
  return PPromise.reject(2)
})
.then(res => {
  console.log('res2',res);
})
.catch(err => {
  console.log('err',err)  // 2
})

1.8 Promise.all()

PPromise.all = (promises) => {
  return new PPromise(function(resolve, reject) {
    const promiseLen = promises.length;
    let resolvedCount = 0;
    const result = [];

    for (let i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(function(value) {
        result[i] = value;
        resolvedCount++;
        if(resolvedCount === promiseLen) {
          resolve(result)
        }
      }, function(error) {
        reject(error)
      })
    }
  })
}

// 测试代码
PPromise.all([
  new PPromise((resolve,reject) => {
    setTimeout(() => {
      resolve(1)
    }, 1000);
  }),
  new PPromise((resolve,reject) => {
    setTimeout(() => {
      resolve(2)
    }, 200);
  })
]).then(res => {
  console.log(res)  [1,2]
})

1.9 Promise.race()

PPromise.race = (promises) => {
  return new PPromise(function(resolve, reject) {
    for (var i = 0; i < promises.length; i++) {
      Promise.resolve(promises[i]).then(function(value) {
        return resolve(value)
      }, function(error) {
        return reject(error)
      })
    }
  })
}

// 测试代码
PPromise.race([
  new PPromise((resolve,reject) => {
    setTimeout(() => {
      resolve(2)
    }, 1000);
  }),
  new PPromise((resolve,reject) => {
    setTimeout(() => {
      resolve(1)
    }, 200);
  })
]).then(res => {
  console.log(res)  // 2
})

2. 相关题

2.1 Promise 的异常捕获

首先回顾下 .catch 和 .then 的源码:

PPromise.prototype.catch = function(onRejected) {
  // .catch 是在当前的 Promise 实例上,调用 then 方法
  return this.then(null, onRejected)
}

PPromise.prototype.then = function(onFulFilled, onRejected) {
    // onRejected 默认代码会 throw error, 这个 error 会被 try catch 住 
    onRejected = onRejected ? onRejected : r => {
        throw r
    }
    return promise2 = new PPromise(function(resolve, reject) {
        ...
        try {
            var x = onResolved(value)
            resolvePromise(promise2, x, resolve, reject)
        } catch(e) {
            return reject(e)
        }
        ...
    }

}

然后我们查看示例一:

// 示例 1
PPromise.reject(1)
.then(res => {           // 第一个 then
  console.log('res',1)
})
.then(res => {  // 第二个 then
  console.log('res',2)
})
.catch(err => {
  // 直接执行到这,前面的 console.log 都不会执行
  console.log(err)  
})
  1. 当 Promise.reject 之后,返回了一个 rejected 状态的 promise
  2. 第一个 then 中,由于没有第二个参数 onRejected 函数,所以默认为 throw Error, 此时又会返回一个 rejected 状态的 promise。
  3. 同理第二个 then 也是这样,返回一个 rejected 状态的 promise
  4. 在最后的 .catch 中,返回的 rejected 的 promise,执行 promise.then(null, err => { console.log(err) })

即由于 Promise rejected之后,在前面没有被代码处理,导致一次次产生新的 rejected 状态的 Promise。

再看看示例二:

// 示例二
PPromise.reject(1)
.then(res => {     // 第一个 then
  console.log('res',1) 
},err => {
    console.log(err);  //  1 
})
.then(res => {
  console.log('res',2)   // 2
})
.catch(err => {
  // 不会执行这里,因为 error 已经在前面捕获了
  console.log(err)  
})
  1. 当 Promise.reject 之后,返回了一个 rejected 状态的 promise
  2. 第一个 then 中,有第二个参数 onRejected 函数,且该函数没有报错或者返回 rejected 状态的 promise,因此返回一个新的 resolved 状态的 promise。

此时示例代码就可以简化如下:

Promise.resolve()
.then(res => {
  console.log('res',2)   // 2
})
.catch(err => {
  console.log(err)  
})

即是否执行 catch 里的代码,与最初的 Promise.reject 无关。

2.2 如何串行执行 Promise

如下三个返回 Promise 的函数,如何让三个函数中的 Promise 依次执行

const promise1 = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise1')
    resolve(1)
  }, 3000);
})

const promise2 = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise2')
    resolve(2)
  }, 2000);
})

const promise3 = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise3')
    resolve(3)
  }, 1000);
})

要完成以上的需求,我们可以使用 promise 链式调用的方式:

Promise.resolve()
.then(res => {
    return promise1(res)
})
.then(res => {
    return promise2(res)
})
.then(res => {
    return promise3(res)
})

然后转用 reduce,可以写出以下的代码:

function serialPromise(promises) {
    promises.reduce((prev, next) => {
        // 该 retunr 是 reduce 语法中的 return
        return prev.then(prevResult => {
            // 该 return 是 promise.then 语法中的 return
            return next(prevResult);
        })
    }, Promise.resolve())
}

// 调用
serialPromise([promise1, promise2, promise3]);  
// 3s   打印 promise1
// 5s   打印 promise2
// 6s   打印 promise3

但是,以上代码中,如果有一个 promise(比如 promise2) reject了,那么执行链就会断掉:

const promise2 = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise2')
    reject(2)
  }, 2000);
})

...

// UnhandledPromiseRejectionWarning: 2 ... 报错

因此我们还需要捕获 promise 的异常,即使 reject 了也要继续执行之后的代码:

function serialPromise(promises) {
    promises.reduce((prev, next) => {
        return prev.then(prevResult => {
            return next(prevResult);
        }).catch(err => {
            return next(err)
        })
    }, Promise.resolve())
}

serialPromise([promise1, promise2, promise3]);  
// 3s   打印 promise1
// 5s   打印 promise2
// 7s   打印 promise2
// 8s   打印 promise3

即最终打印了两遍 promise2, 这是为什么呢?

其实以上的代码,还原回正常的代码应该是:

Promise.resolve()
.then(res => {
    // 1
    return promise1(res)
})
.catch(err => {
    // 2
  return promise1(err)
})
.then(res => {    // 第一次
    // 3
    return promise2(res)
})
.catch(err => {   // 第二次
    // 4
  return promise2(err)
})
.then(res => {
    // 5
    return promise3(res)
})
.catch(err => {
    // 6
  return promise3(err)
})

执行顺序: 1 3 4 6

即以上代码有两个问题:

  1. 在第一次执行的时候,会执行一遍 promise2, 而此时返回的 Promise 是 reject 状态的, 会进入 .catch 执行第二次,这时候又会再执行一遍 promise2
  2. 第二次的 promise 依旧是 reject 状态,所以最终执行的是 6, 而不是 5,虽然没啥问题,但就是觉得怪怪的

我们可以想到以下的代码,可以处理这个第一个问题:

Promise.resolve()
.then(res => {
    // 1
    return promise1(res)
},err => {
    // 2
    return promise1(err)
})
.then(res => {
    // 3
    return promise2(res)
},err => {
    // 4
    return promise2(err)
})
.then(res => {
    // 5
    return promise3(res)
},err => {
    // 6
    return promise3(err)
})

执行顺序: 1 4 6

即依然会有第二个问题。

另外一种解决方案是:

const serialPromises3=function(promises){
  const process=function(i,args){
    const curr=promises[i]
    // 由于 next 函数返回 undefined,不返回 rejected 的 promise
    // 所以不会存在执行两遍的情况
    // 而且之后的 promise 也是走的 onFulFilled 不是 onRejected
    const next=function(res){process(i+1,res)}  
    if(curr) {
      curr(args)
      .then(next)
      .catch(next)
    }
  }
  process(0)
}

2.3 如果 resolve 一个 Promise, 结果会如何?

面试题,以下代码会打印什么结果?

new Promise(resolve => {
    resolve(Promise.reject())
})
.then(res => {
    console.log('success')
},err => {
    console.log('error')
})

放到控制台执行就知道了会打印 error, 为什么呢?按照我们的源码,我不管 resolve 的值是什么,只要是 resolve, 就应该执行 onFulFilled 才对。

但是,其实 有个 issue 里讨论过这个:

Q: 为什么 Promise 构造函数里面的 resolve 不需要处理 value 有可能是 thenable 的逻辑?

A: 问的很好。实际上是需要的。ES6 Promise中就对此种情况做出了定义。
Promise/A+标准并没有对构造函数的签名及行为做出定义,只定了then方法的行为。所以Promise/A+的测试中并不包含测试构造函数的用例。

也就是说,我们平常使用到的 Promise 不仅仅实现了 Promise/A+ 的规范,同时对构造函数部分的代码也做了处理。因此我们上面的源码不能反映 ES6 中 Promsie 的结果

于是我找到了 ES6 Promise , 且测试了结果确实是打印 error。

于是我们想到,如果 resolve 中的结果是个 promise,我们应该将它的值作为 new Promise 的值,于是改进代码:

function resolve(value) {
  setTimeout(function () {
    if (self.status !== 'pending') {
      return
    }
    self.status = 'resolved'
    self.data = value

    if (value && value.then) {
        // 如果 resolve 一个 promise,则将 value.then 来决定当前 promise 的值
      value.then(res => {
        for (var i = 0; i < self.callbacks.length; i++) {
          self.callbacks[i].onResolved(value)
        }
      }, err => {
        for (var i = 0; i < self.callbacks.length; i++) {
          self.callbacks[i].onRejected(value)
        }
      })
    } else {
      for (var i = 0; i < self.callbacks.length; i++) {
        self.callbacks[i].onResolved(value)
      }
    }
  })
}

而查看 es6-promise, 确实在 resolve 的时候会有取 value.then 相关的代码(具体逻辑暂不研究):

function resolve(promise, value) {
  if (promise === value) {
    reject(promise, selfFulfillment());
  } else if (objectOrFunction(value)) {
    var then$$1 = void 0;
    try {
      then$$1 = value.then;  // 取 value.then
    } catch (error) {
      reject(promise, error);
      return;
    }
    handleMaybeThenable(promise, value, then$$1);
  } else {
    fulfill(promise, value);
  }
}

于是,我又想,是不是在 reject 的时候,也会如此呢?

new Promise((resolve,reject) => {
    reject(Promise.resolve())
})
.then(res => {
    console.log('success')
},err => {
    console.log('error')
})

结果发现并不是,结果还是打印 error , 因为 es6-promise 中的 reject 并没有进行 value.then 的处理(粗略翻看代码,并没有 value.then 的操作)。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

骄傲

暂无简介

文章
评论
27 人气
更多

推荐作者

夢野间

文章 0 评论 0

百度③文鱼

文章 0 评论 0

小草泠泠

文章 0 评论 0

zhuwenyan

文章 0 评论 0

weirdo

文章 0 评论 0

坚持沉默

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文