type
Post
status
Published
date
Jul 1, 2022
slug
code-promise
summary
手写Promise是前端面试经常考察的点。实际上只要熟悉Promise A+规范,并不很难,一般面试也不要求写得很全。
tags
前端
开发
category
学习思考
icon
password
Property
Nov 13, 2022 07:24 AM
Promise 作为 JavaScript 语言的一个重要特性深受广大开发者喜爱。它在 ECMAScript 2015 版本中加入,提供了对异步过程的优雅封装。再加上 ES7 版本加入的异步函数语法,Promise 已经成为极为重要的 JavaScript 编程工具。
标准 Promise 实现需要遵循 Promise A+规范,那么我们能不能尝试自己去手动实现一个呢?

基础版本

可以先来回顾一下 Promise 最基本的使用方式:
const p1 = new Promise((resolve, reject) => { console.log('create a promise') resolve('success') }) const p2 = p1.then(data => { console.log(data) throw new Error('rejected') }) const p3 = p2.then( data => { console.log('success', data) }, err => { console.log('failed', err) } )
首先,我们在调用Promise构造函数后,会返回一个Promise对象;构造函数需要传入一个函数参数,称为执行函数,Promise 的主要工作代码都在执行函数中;如果执行函数中的工作成功了,那么就会调用resolve()函数,如果失败了,会调用reject函数;Promise 的状态是不可由外部代码改变的,并且一旦落定就不可逆。
结合Promise/A+规范,我们可以分析出 Promise 的基本特征:
  1. Promise 实例有三个状态:pendingfulfilledrejected
  1. 调用Promise构造函数来创建一个 Promise,并且需要传入一个执行函数,它会立刻运行;
  1. 执行函数接受两个参数,分别是resolvereject
  1. Promise 的默认状态是pending
  1. Promise 有一个value用来保存成功状态的值,可以是undefinedthenable或者 Promise;
  1. Promise 有一个reason用来保存失败状态的值;
  1. Promise 只能从pendingrejected,或者从pendingfulfilled,状态一旦确认,就不会再改变了;
  1. Promise 必须有一个then方法,它接受两个参数,分别是 Promise 成功的回调onFulfilled,还有 Promise 失败的回调onRejected
  1. 如果调用then时,Promise 已经成功,那么执行onFulfilled,参数是 Promise 的value
  1. 如果调用then时,Promise 已经失败,那么执行onRejected,参数是 Promise 的reason
  1. 如果then中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调onRejected
按照以上特征,我们可以尝试实现一个粗略的 Promise:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { constructor(executor) { // 默认状态 pending this.status = PENDING // 存放成功状态的值 this.value = undefined // 存放失败状态的值 this.reason = undefined // 成功后调用的方法 const resolve = value => { // 状态为PENDING时才可以更新状态,防止executor中调用了两次resolve/reject方法 if (this.status === PENDING) { this.status = FULFILLED this.value = value } } // 失败后调用的方法 const reject = reason => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason } } try { // 立即执行,将resolve和reject函数传递给执行函数 executor(resolve, reject) } catch (error) { reject(error) } } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } } } function test() { const promise = new MyPromise((resolve, _) => { resolve('成功') }).then( data => { console.log('success', data) }, error => { console.log('failed', error) } ) } // success 成功 test()
现在我们已经实现了一个最简易的基础版本的 Promise,它有很多不足,最大的一个问题就是不能处理异步操作。如果我们在执行函数中进行异步操作,那么通过 Promise.prototype.then 添加的处理函数没有作用。
function test() { const promise = new MyPromise((resolve, _) => { setTimeout(() => { resolve('成功') }, 1000) }).then( data => { console.log('success', data) }, error => { console.log('failed', error) } ) } test() // 没有作用
因为 Promise 实例调用then()方法时,当前的 Promise 并没有成功,一直处于pending状态。所以在调用then()方法设置成功和失败的回调时,我们需要将其先保存起来,再在 Promise 落定时调用设置好的回调。
结合这个思路我们优化一下代码:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { constructor(executor) { // 默认状态 pending this.status = PENDING // 存放成功状态的值 this.value = undefined // 存放失败状态的值 this.reason = undefined // 存放成功的回调 this.onResolvedCallbacks = [] // 存放失败的回调 this.onRejectedCallbacks = [] // 成功后调用的方法 const resolve = value => { // 状态为PENDING时才可以更新状态,防止executor中调用了两次resolve/reject方法 if (this.status === PENDING) { this.status = FULFILLED this.value = value this.onResolvedCallbacks.forEach(fn => fn()) } } // 失败后调用的方法 const reject = reason => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason this.onRejectedCallbacks.forEach(fn => fn()) } } try { // 立即执行,将resolve和reject函数传递给执行函数 executor(resolve, reject) } catch (error) { reject(error) } } then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value) } if (this.status === REJECTED) { onRejected(this.reason) } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { onFulfilled(this.value) }) this.onRejectedCallbacks.push(() => { onRejected(this.reason) }) } } }
现在,异步问题已经解决了!

then的链式调用和值穿透

我们都知道 Promise 有一个很方便的特性是可以链式调用。在我们使用 Promise 的时候,当then函数中return了一个值,不管是什么值,我们都能在下一个then中获取到,这就是 then的链式调用。而且,当我们不在then中传入参数,例如:promise.then().then()时,那么后面的then依旧能够得到前面的then()的返回值,这就是所谓值的穿透。那么具体应该怎么实现呢?简单思考一下,如果每次调用then()的时候,我们都创建一个新的 Promise 对象,并且将上一个then的返回结果传给这个新的 Promise 的then方法,不就可以一直then下去了么?
有了上面的想法,再结合 Promise/A+规范梳理一下要点:
  1. then的参数onFulfilledonRejected可以缺省,如果onFulfilled或者onRejected不是函数,将其忽略,且依旧可以在下面的then中获取到之前返回的值;
  1. Promise 可以then多次,每次执行完then()方法之后返回的都是一个新的 Promise;
  1. 如果then的返回值x是一个普通的值,那么就会把这个结果作为参数,传递给下一个then的成功的回调;
  1. 如果then中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调;
  1. 如果then的返回值x是一个 Promise,那么就等待这个 Promise 执行完。Promise 如果成功,就走下一个then的成功回调;如果失败或者抛出异常就走下一个then的失败回调;
  1. 如果then的返回值x和 Promise 是同一个引用对象,造成循环引用则抛出异常,将异常传递给下一个then的失败回调;
  1. 如果then的返回值x是一个 Promise,且x同时调用resolvereject函数,则第一次调用优先,其他所有调用被忽略。
按照这些要求,我们将代码补充完整:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' const resolvePromise = (promise, x, resolve, reject) => { if (promise === x) { return reject( new TypeError('Chaining cycle detected for promise #<Promise>') ) } let called if ((typeof x === 'object' && x !== null) || typeof x === 'function') { try { let then = x.then if (typeof then === 'function') { then.call( x, y => { if (called) return called = true resolvePromise(promise, y, resolve, reject) }, r => { if (called) return called = true reject(r) } ) } else { resolve(x) } } catch (err) { if (called) { return } called = true reject(err) } } else { resolve(x) } } class MyPromise { constructor(executor) { // 默认状态 pending this.status = PENDING // 存放成功状态的值 this.value = undefined // 存放失败状态的值 this.reason = undefined // 存放成功的回调 this.onResolvedCallbacks = [] // 存放失败的回调 this.onRejectedCallbacks = [] // 成功后调用的方法 const resolve = value => { // 状态为PENDING时才可以更新状态,防止executor中调用了两次resolve/reject方法 if (value instanceof Promise) { return value.then(resolve, reject) } if (this.status === PENDING) { this.status = FULFILLED this.value = value this.onResolvedCallbacks.forEach(fn => fn()) } } // 失败后调用的方法 const reject = reason => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason this.onRejectedCallbacks.forEach(fn => fn()) } } try { // 立即执行,将resolve和reject函数传递给执行函数 executor(resolve, reject) } catch (error) { reject(error) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : v => v const _promise = new Promise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { const x = onFulfilled(this.value) resolvePromise(_promise, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === REJECTED) { setTimeout(() => { try { const x = onRejected(this.reason) resolvePromise(_promise, x, resolve, reject) } catch (e) { reject(e) } }, 0) } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value) resolvePromise(_promise, x, resolve, reject) } catch (e) { reject(e) } }, 0) }) this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason) resolvePromise(_promise, x, resolve, reject) } catch (e) { reject(e) } }, 0) }) } }) return _promise } } function test() { const promise = new Promise((resolve, reject) => { reject('失败') }) .then() .then() .then( data => { console.log(data) }, err => { console.log('err', err) } ) } test() // 失败 err
至此我们已经完成了 Promise 最关键的部分:then的链式调用和值穿透。

实现 Promise 的其他 API

我们已经按照符合 Promise/A+规范的方式实现了一个Promise类,但原生的 Promise 还提供了一些其他方法,例如:
  • Promise.resolve()
  • Promise.reject()
  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.all()
  • Promise.race()
下面我们逐一实现这些方法。

Promise.resolve()

它默认产生一个成功的Promise,如果传入的参数不是一个值而是一个 Promise,那么会等待这个 Promise 落定。
static resolve(data) { return new Promise((resolve, reject) => { resolve(data) }) }

Promise.reject()

它默认产生一个失败的 Promise,Promise.reject是直接将值变为错误结果。
static reject(reason) { return new Promise((resolve, reject) => { reject(reason) }) }

Promise.prototype.catch()

Promise.prototype.catch用来捕获 Promise 的异常,实际上是then的一种简写。
Promise.prototype.catch = function (errCallback) { return this.then(null, errCallback) }

Promise.prototype.finally

finally 并不表示最终的意思,而表示无论如何都会执行的意思。
Promise.prototype.finally = function (callback) { return this.then( value => { return Promise.resolve(callback()).then(() => value) }, reason => { return Promise.resolve(callback()).then(() => { throw reason }) } ) }

Promise.all

Promise.all用来解决并发多个 Promise 的问题,获取最终的结果(如果有一个失败则失败)。
static all(promises) { return new Promise((resolve, reject) => { const resultArr = [] let orderIdx = 0 const processResultByKey = (value, index) => { resultArr[index] = value if (++orderIndex === promises.length) { resolve(resultArr) } } for (let i = 0; i < values.length; i++) { const promise = promises[i] if (promise && typeof promise.then === 'function') { promise.then(value => { processResultByKey(value, i) }, reject) } else { procesResultByKey(value, i) } } }) }

Promise.race

Promise.race用来处理多个异步过程,采用最快的结果。
static race(promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; i++) { const val = promises[i] if (val && typeof val.then === 'function') { val.then(resolve, reject) } else { resolve(val) } } }) }

完整代码

const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' const resolvePromise = (promise, x, resolve, reject) => { if (promise === x) { return reject( new TypeError('Chaining cycle detected for promise#<MyPromise>') ) } let called if ((typeof x === 'object' && x !== null) || typeof x === 'function') { try { const then = x.then if (typeof then === 'function') { then.call( x, y => { if (called) return called = true resolvePromise(promise, y, resolve, reject) }, r => { if (called) return called = true reject(r) } ) } else { resolve(x) } } catch (err) { if (called) return called = true reject(err) } } } class MyPromise { constructor(executor) { this.status = PENDING this.value = undefined this.reason = undefined this.onResolvedCallbacks = [] this.onRejectedCallbacks = [] const resolve = value => { if (value instanceof Promise) { return value.then(resolve, reject) } if (this.status === PENDING) { this.status = FULFILLED this.value = value this.onResolvedCallbacks.forEach(fn => fn()) } } const reject = reason => { if (this.status === PENDING) { this.status = REJECTED this.reason = reason this.onRejectedCallbacks.forEach(fn => fn()) } } try { executor(resolve, reject) } catch (err) { reject(err) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err } const _promise = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.value) resolvePromise(_promise, x, resolve, reject) } catch (err) { reject(err) } }, 0) } if (this.status === REJECTED) { setTimeout(() => { try { const x = onRejected(this.reason) resolvePromise(_promise, x, resolve, reject) } catch (err) { reject(err) } }, 0) } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value) resolvePromise(_promise, x, resolve, reject) } catch (err) { reject(err) } }, 0) }) this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason) resolvePromise(_promise, x, resolve, reject) } catch (err) { reject(err) } }, 0) }) } }) return _promise } catch(errCallback) { return this.then(null, errCallback) } finally(callback) { return this.then( value => { return MyPromise.resolve(callback().then(() => value)) }, reason => { return MyPromise.resolve( callback().then(() => { throw reason }) ) } ) } static all(values) { if (!Array.isArray(values)) { const type = typeof values return new TypeError(`TypeError: ${type} ${values} is not iterable`) } return new MyPromise((resolve, reject) => { const resultArr = [] let orderIdx = 0 const processResultByKey = (value, index) => { resultArr[index] = value if (++orderIdx === values.length) { resolve(resultArr) } } for (let i = 0; i < values.length; i++) { const value = values[i] if (value && typeof value.then === 'function') { value.then(value => { processResultByKey(value, i) }, reject) } else { processResultByKey(value, i) } } }) } static race(values) { if (!Array.isArray(values)) { const type = typeof values return new TypeError(`TypeError: ${type} ${values} is not iterable`) } return new MyPromise((resolve, reject) => { for (let i = 0; i < values.length; i++) { const val = values[i] if (val && typeof val.then === 'function') { val.then(resolve, reject) } else { resolve(val) } } }) } static resolve(data) { return new MyPromise(resolve => { resolve(data) }) } static reject(reason) { return new Promise((_, reject) => { reject(reason) }) } } const promise = new MyPromise((resolve, reject) => { reject('失败') }) .then() .then() .then( data => { console.log(data) }, err => { console.error('error', err) } )
V8引擎中的对象属性访问使用ESLint、Prettier统一团队代码