Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
以上出自 ECMAScript 6 入门 阮一峰
1、提前准备
创建文件夹,进入文件夹,初始化为 npm 项目,编辑 package.json 添加执行脚本用于快速运行任务,同时添加 type 属性,其值为 module
执行 npm 命令
npm init
修改 package.json 完整信息如下:
{ "name": "my_promise", "type": "module", "scripts": { "dev": "node src/myPromise.js" } }
封装 isFunction 函数,用于判断传入值 value 的类型是否为函数
/** * 传入值否为函数 * @param {*} value 传入value * @returns boolean */ const isFunction = value => typeof value === 'function'
封装 isObject 函数,判断传入值 value 的类型是否为对象
/** * 传入值类型是否为对象 * @param {*} value 传入值 * @returns boolean */ const isObject = value => Object.prototype.toString.call(value) === '[object Object]'
Promise 对象状态,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)
// 等待 const PENDING = 'pending' // 成功 const FULFILLED = 'fulfilled' // 失败 const REJECTED = 'rejected'
2、基本方法
Promise 对象构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。resolve 函数的作用是,将 Promise 对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject 函数的作用是,将 Promise 对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
创建构造函数,实现 resolve 和 reject 函数的定义,在创建 Promise 的时候传入,根据调用的结果调用不同的函数
class MyPromise { /** * 初始化函数 * @param { Function } executor 执行函数 */ constructor(executor) { /** * 异步操作成功时调用 * @param {*} value 操作成功结果 * @returns void */ const resolve = value => {} /** * 异步操作失败时调用 * @param {*} reason 错误信息 * @returns void */ const reject = reason => {} // 执行外部传入参数 executor(resolve, reject) } }
添加 Promise 状态和返回结果,Promise 对象的状态不受外界影响,一旦状态改变,就不会再变,任何时候都可以得到这个结果。
class MyPromise { constructor(executor) { // 保存异步操作执行结果 this.result = null // 记录当前Promise状态 this.status = PENDING } }
修改 resolve 和 reject 函数,添加当前对象状态修改和异步执行结果保存功能
class MyPromise { constructor(executor) { const resolve = value => { // 若当前状态已经被修改则不可修改状态 if (this.state !== PENDING) return // 修改状态为执行成功 this.state = FULFILLED // 保存操作成功结果 this.result = value } const reject = reason => { if (this.state !== PENDING) return // 修改状态为执行失败 this.state = REJECTED // 保存执行失败错误信息 this.result = reason } } }
当前完整代码点击查看
3、添加 then 方法
实现 then 方法
then 方法的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then 方法的第一个参数是 resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数,它们都是可选的class MyPromise { /** * 添加状态改变时的回调函数 * @param {*} onFulfilled FULFILLED状态回调函数 * @param {*} onRejected FULFILLED状态的回调函数 */ then(onFulfilled, onRejected) {} }
添加 callbacks 字段,初始化为空数组,若当前状态为 pending 则用于保存 then 所传入的函数
class MyPromise { constructor(executor) { ... // 用于记录then函数的参数信息 this.callbacks = [] } then(onFulfilled, onRejected) { // 创建回调信息 const callback = { onFulfilled, onRejected } // 当前状态为等待中 if (this.state === PENDING) { // 保存回调信息到callbacks数组中 this.callbacks.push(callback) return } } }
添加 \_runCallback 函数,若当前状态不为 pending 则 then 传入的函数,若状态为 FULFILLED 同时 onFulfilled 类型为函数,则传入 result 作为参数执行 onFulfilled 函数。若状态为 REJECTED 同时 onRejected 类型为函数,则传入 result 作为参数执行 onRejected 函数
class MyPromise { then(onFulfilled, onRejected) { // 执行当前任务 this._runCallback(callback) } /** * 执行then传入的函数信息 * @param {object} callback 需要执行的回调函数信息 * @param {*} callback.onFulfilled 成功状态下回调函数 * @param {*} callback.onRejected 失败状态下回调函数 */ _runCallback({ onFulfilled, onRejected }) { // 成功状态 if (this.state === FULFILLED) { // 若onFulfilled为函数,则传入result作为参数执行 isFunction(onFulfilled) && onFulfilled(this.result) } // 失败状态 if (this.state === REJECTED) { // 若onRejected为函数,则传入result作为参数执行 isFunction(onRejected) && onRejected(this.result) } } }
当前完整代码点击查看
4、添加异步执行功能
当 then 调用的时候,如果当前状态为 PENDING,就会保存传入参数到当前 Promise 对象中,等待 Promise 结果完成后,调用已保存的函数执行。
添加\_runAllCallbacks 函数,将已经加入到 callbacks 的函数循环调用执行
class MyPromise { /** * 执行callbacks数组中保存的 */ _runAllCallbacks = () => { // 循环调用数组元素执行 this.callbacks.forEach(this._runCallback) // 完成后清空数组 this.callbacks = [] } }
实现 try..catch 包裹 executor,若执行的时候抛出异常则调用 reject 函数
class MyPromise { constructor(executor) { try { // 执行外部传入参数 executor(resolve, reject) } catch (error) { // 若executor抛出异常则调用reject函数 reject(error) } } }
完善 resolve 和 reject 函数,执行的时候如果 callbacks 不为空,则运行数组中的信息
class MyPromise { constructor(executor) { const resolve = value => { // 执行数组中的函数 this._runAllCallbacks() } const reject = reason => { // 执行数组中的函数 this._runAllCallbacks() } } }
将任务添加通过 queueMicrotask 添加至微任务队列中,queueMicrotask 是一个简短的函数,它将在当前任务(task)完成其工作之后运行,并且在执行上下文的控制返回到浏览器的事件循环之前,没有其他代码等待运行
class MyPromise { constructor(executor) { const resolve = value => { // 将数组中的函数在微任务队列中运行 queueMicrotask(this._runAllCallbacks) } const reject = reason => { // 将数组中的函数在微任务队列中运行 queueMicrotask(this._runAllCallbacks) } } then(onFulfilled, onRejected) { // 在微任务队列中执行当前任务 queueMicrotask(() => this._runCallback(callback)) } }
当前完整代码点击查看
5、实现 resolvePromise 功能
用于处理 resolve 函数传入参数 value 的一些问题,若 resolve 传入 Promise 或者 Promise like 的函数时需要处理相关情况
若 value 为 this 的时候,抛出 TypeError,并且执行 onRejected 函数
class MyPromise { constructor(executor) { /** * 处理resolve传入参数信息 * @param {*} value resolve传入参数 */ const resolvePromise = value => { // 传入value为当前对象时,抛出异常 if (value === this) { return reject(new TypeError('Chaining cycle detected for promise')) } } } }
若 value 类型为 MyPromise 对象时候,则将当前 resolve 和 reject 传入 value then 函数之中
class MyPromise { constructor(executor) { const resolvePromise = value => { ... // 当前value为MyPromise则执行函数,则将当前resolve和reject传入value then之中 if (value instanceof MyPromise) { return value.then(resolve, reject) } } } }
若 value 为一个对象或者函数当其中包含一个 then 函数,则执行函数
class MyPromise { constructor(executor) { const resolvePromise = value => { // 当value值为对象的时候 if (isObject(value) || isFunction(value)) { try { // 如果value包含一个then字段 const then = value.then // 如果then为一个函数 if (isFunction(then)) { // 进入一个新的MyPromise对象 return new MyPromise(then.bind(value)).then(resolve, reject) } } catch (error) { // 如果抛出异常则调用reject函数 return reject(error) } } } } }
若为其他,则修改当前状态,将 value 作为参数调用 resolve 之中
class MyPromise { constructor(executor) { const resolvePromise = value => { ... // 若为其他类型则直接调用resolve函数 resolve(value) } try { // 使用resolvePromise代替resolve函数 executor(resolvePromise, reject) } catch (error) { // 若executor抛出异常则调用reject函数 reject(error) } } }
多次执行 resolve/reject 函数问题处理
- 如果传入的为一个 Promise、MyPromise 或者其他包含 then 函数的对象是,会创建一个全新的 MyPromie 对象,在其 then 函数中执行,如若 resolve 执行两次则第一个为 thenable 对象,则会放入微任务队列中执行,导致执行第二个函数执行,所以需要添加限制保证 reslove/reject 函数只能执行一次
/** * 预期输出 data: aaa * 实际输出 data: bbb */ const p = new MyPromise((resolve, reject) => { resolve({ then: resolve => resolve('aaa') }) resolve('bbb') }) p.then(data => { console.log('data: ', data) })
- 处理函数执行调用问题,通过添加 isExecuted 记录函数执行状态,若有一个函数被执行则拒绝执行其他函数
class MyPromise { constructor(executor) { // 用于记录constructor的参数执行状态 let isExecuted = false try { // 执行外部传入参数 executor( value => { if (isExecuted) return isExecuted = true resolvePromise(value) }, reason => { if (isExecuted) return isExecuted = true reject(reason) } ) } catch (error) { if (isExecuted) return isExecuted = true // 若executor抛出异常则调用reject函数 reject(error) } } }
修改后执行结果符合预期
/** * 预期输出 data: aaa * 实际输出 data: aaa */ const p = new MyPromise((resolve, reject) => { resolve({ then: resolve => resolve('aaa') }) resolve('bbb') }) p.then(data => { console.log('data: ', data) })
函数封装和命名修改
控制 reject 和 resolve 函数执行时,存在大量重复代码,可以使用函数将其封装
class MyPromise { constructor(executor) { // 用于记录constructor的参数执行状态 let isExecuted = false /** * 用于限制函数执行次数 * @param {Function} fn 需要限制的函数 * @returns 若isExecuted为true则函数不会执行 */ const executedHandler = fn => result => { if (isExecuted) return isExecuted = true isFunction(fn) && fn(result) } try { // 执行外部传入参数 executor(executedHandler(resolvePromise), executedHandler(reject)) } catch (error) { // 若executor抛出异常则调用reject函数 executedHandler(reject)(error) } } }
resolve 和 reject 为 executor 函数中的参数,为了便于理解,将当前 resolve 函数重新命名为 onFulfilled ,将 reject 函数重命名为 onRejected
class MyPromise { constructor(executor) { /** * 异步操作成功时调用 * @param {*} value 操作成功结果 * @returns void */ const onFulfilled = value => {} /** * 异步操作失败时调用 * @param {*} reason 错误信息 * @returns void */ const onRejected = reason => {} try { // 执行外部传入参数 executor(executedHandler(resolvePromise), executedHandler(onRejected)) } catch (error) { // 若executor抛出异常则调用reject函数 executedHandler(onRejected)(error) } } }
因为 onFulfilled 和 onRejected 实际完成的任务相似,所以添加一个新的函数 resultHandler 通过传入参数进行生成 onFulfilled 和 onRejected
class MyPromise { constructor(executor) { /** * 通过传递状态生成onFulfilled或者onRejected函数 * @param {*} state 需要生成的函数其状态 * @returns onFulfilled | onRejected */ const resultHandler = state => result => { // 若当前状态已经被修改则不可修改状态 if (this.state !== PENDING) return // 修改状态为执行成功 this.state = state // 保存操作成功结果 this.result = value // 将数组中的函数在微任务队列中运行 queueMicrotask(this._runAllCallbacks) } /** * 异步操作成功时调用 * @param {*} value 操作成功结果 * @returns void */ const onFulfilled = resultHandler(FULFILLED) /** * 异步操作失败时调用 * @param {*} reason 错误信息 * @returns void */ const onRejected = resultHandler(REJECTED) } }
当前完整代码点击查看
6、实现 then 函数链式调用
then 函数需要实现链式调用,则需要返回一个新的 Promise,将当前 then 方法中的 onFulfilled 或 onRejected 的返回值作为新 Promise 的 reslove 参数,若当前 then 函数的 onFulfilled 或 onRejected 不存在,则将上一层级 Promise 执行结果传递至新的 Promise,同时若当前 then 方法中的参数执行抛出异常,则执行下一层 Promise 的 reject 函数
修改 then 方法返回值
class MyPromise { /** * 添加状态改变时的回调函数 * @param {*} onFulfilled FULFILLED状态回调函数 * @param {*} onRejected FULFILLED状态的回调函数 */ then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { // 创建回调信息,将需要返回的Promise执行函数保存至callback之中 const callback = { resolve, reject, onFulfilled, onRejected } // 当前状态为等待中 if (this.state === PENDING) { // 保存回调信息到callbacks数组中 this.callbacks.push(callback) return } // 在微任务队列中执行当前任务 queueMicrotask(() => this._runCallback(callback)) }) } }
修改 \_runCallback 执行信息,将 then 传递的参数和需要返回的 Promise 的 resolve 和 reject 根据函数执行状态处理返回 Promise 的结果
class MyPromise { /** * 执行then传入的函数信息 * @param {object} callback 需要执行的回调函数信息 * @param {Function} callback.resolve then函数返回的Promise reject函数 * @param {Function} callback.reject then函数返回的Promise reject函数 * @param {*} callback.onFulfilled 成功状态下回调函数 * @param {*} callback.onRejected 失败状态下回调函数 */ _runCallback = ({ resolve, reject, onFulfilled, onRejected }) => { try { // 成功状态 if (this.state === FULFILLED) { // 若onFulfilled为函数,则传入result作为参数执行,并作为resolve参数传递给then返回的Promise之中 // 若onFulfilled不为函数,直接传递当前result至返回的Promise resolve之中 isFunction(onFulfilled) ? resolve(onFulfilled(this.result)) : resolve(this.result) } // 失败状态 if (this.state === REJECTED) { // 若onRejected为函数,则传入result作为参数执行,并作为resolve参数传递给then返回的Promise之中 // 若onRejected不为函数,直接传递当前result至返回的Promise reject之中 isFunction(onRejected) ? resolve(onRejected(this.result)) : reject(this.result) } } catch (error) { // 若当前函数执行抛出,直接调用返回的Promise reject方法 reject(error) } } }
当前完整代码点击查看
7、使用 promises-aplus-tests 测试
安装 promises-aplus-tests
npm install promises-aplus-tests -D
编写测试 adapter
import PromiseAplusTests from 'promises-aplus-tests' import MyPromise from '../src/myPromiseSimple.js' const adapter = { resolved(value) { return new MyPromise((resolve, reject) => { resolve(value) }) }, rejected(reason) { return new MyPromise((resolve, reject) => { reject(reason) }) }, deferred() { let dfd = {} dfd.promise = new MyPromise((resolve, reject) => { dfd.resolve = resolve dfd.reject = reject }) return dfd } } PromiseAplusTests(adapter, err => { console.log(err) })
当前完整代码点击查看
8、TS 版本
添加工具函数类型
enum STATE { PENDING, FULFILLED, REJECTED } type TResolveFn<T = any> = (value: T) => void type TRejectFn = (value: any) => void type TOnFulfilledFn<T = any, R = any> = (data: T) => R type TOnRejectedFn = (data: any) => any type TPromiseExecutor<T = unknown> = (resolve: TResolveFn<T>, reject: TRejectFn) => void interface ICallback { resolve: TResolveFn reject: TRejectFn onFulfilled?: TOnFulfilledFn onRejected?: TOnRejectedFn } const isFunction = (value: any): value is Function => typeof value === 'function' const isObject = (value: any): value is object => Object.prototype.toString.call(value) === '[object Object]'
实现 resolve 的类型
export default class MyPromise<T> { private result: any private state: STATE = STATE.PENDING private callbacks: ICallback[] = [] constructor(executor: TPromiseExecutor<T>) { try { executor(executeHandler<T>(resolvePromise), executeHandler(onRejected)) } catch (error) { executeHandler(onRejected)(error) } } }
添加 then 方法的类型
export default class MyPromise<T> { then<R>(onFulfilled?: TOnFulfilledFn<T, R>, onRejected?: TOnRejectedFn) { return new MyPromise<R>((resolve, reject) => { const task = { onFulfilled, onRejected, resolve, reject } if (this.state === STATE.PENDING) { this.callbacks.push(task) return } queueMicrotask(() => this._runTask(task)) }) } }
当前完整代码点击查看