本篇来讲讲如何模拟实现一个 Promise 的基本功能,网上这类文章已经很多,本篇笔墨会比较多,因为想用自己的理解,用白话文来讲讲
Promise 的基本规范,参考了这篇:【翻译】Promises/A+规范
但说实话,太多的专业术语,以及基本按照标准规范格式翻译而来,有些内容,如果不是对规范的阅读方式比较熟悉的话,那是很难理解这句话的内容的
我就是属于没直接阅读过官方规范的,所以即使在看中文译版时,有些表达仍旧需要花费很多时间去理解,基于此,才想要写这篇
Promise 基本介绍Promise 是一种异步编程方案,通过 then 方法来注册回调函数,通过构造函数参数来控制异步状态
Promise 的状态变化有两种,成功或失败,状态一旦变更结束,就不会再改变,后续所有注册的回调都能接收此状态,同时异步执行结果会通过参数传递给回调函数
使用示例 var p = new Promise((resolve, reject) => { // do something async job // resolve(data); // 任务结束,触发状态变化,通知成功回调的处理,并传递结果数据 // reject(err); // 任务异常,触发状态变化,通知失败回调的处理,并传递失败原因 }).then(value => console.log(value)) .catch(err => console.error(err)); p.then(v => console.log(v), err => console.error(err));上述例子是基本用法,then 方法返回一个新的 Promise,所以支持链式调用,可用于一个任务依赖于上一个任务的执行结果这种场景
对于同一个 Promise 也可以调用多次 then 来注册多个回调处理
通过使用来理解它的功能,清楚它都支持哪些功能后,我们在模拟实现时,才能知道到底需要写些什么代码
所以,这里来比较细节的罗列下 Promise 的基本功能:
Promise 有三种状态:Pending(执行中)、Resolved(成功)、Rejected(失败),状态一旦变更结束就不再改变
Promise 构造函数接收一个函数参数,可以把它叫做 task 处理函数
task 处理函数用来处理异步工作,这个函数有两个参数,也都是函数类型,当异步工作结束,就是通过调用这两个函数参数来通知 Promise 状态变更、回调触发、结果传递
Promise 有一个 then 方法用于注册回调处理,当状态变化结束,注册的回调一定会被处理,即使是在状态变化结束后才通过 then 注册
then 方法支持调用多次来注册多个回调处理
then 方法接收两个可选参数,这两个参数类型都是函数,也就是需要注册的回调处理函数,分别是成功时的回调函数,失败时的回调函数
这些回调函数有一个参数,类型任意,值就是任务结束需要通知给回调的结果,通过调用 task 处理函数的参数(类型是函数)传递过来
then 方法返回一个新的 Promise,以便支持链式调用,新 Promise 状态的变化依赖于回调函数的返回值,不同类型处理方式不同
then 方法的链式调用中,如果中间某个 then 传入的回调处理不能友好的处理回调工作(比如传递给 then 非函数类型参数),那么这个工作会继续往下传递给下个 then 注册的回调函数
Promise 有一个 catch 方法,用于注册失败的回调处理,其实是 then(null, onRejected) 的语法糖
task 处理函数或者回调函数执行过程发生代码异常时,Promise 内部自动捕获,状态直接当做失败来处理
new Promise(task) 时,传入的 task 函数就会马上被执行了,但传给 then 的回调函数,会作为微任务放入队列中等待执行(通俗理解,就是降低优先级,延迟执行,不知道怎么模拟微任务的话,可以使用 setTimeout 生成的宏任务来模拟)
这些基本功能就足够 Promise 的日常使用了,所以我们的模拟实现版的目标就是实现这些功能
模拟实现思路 第一步:骨架Promise 的基本功能清楚了,那我们代码该怎么写,写什么?
从代码角度来看的话,无非也就是一些变量、函数,所以,我们就可以来针对各个功能点,思考下,都需要哪些代码:
变量上至少需要:三种状态、当前状态(_status)、传递给回调函数的结果值(_value)
构造函数 constructor
task 处理函数
task 处理函数的两个用于通知状态变更的函数(handleResolve, handleReject)
then 方法
then 方法注册的两个回调函数
回调函数队列
catch 方法
task 处理函数和注册的回调处理函数都是使用者在使用 Promise 时,自行根据业务需要编写的代码
那么,剩下的也就是我们在实现 Promise 时需要编写的代码了,这样一来,Promise 的骨架其实也就可以出来了:
export type statusChangeFn = (value?: any) => void; /* 回调函数类型 */ export type callbackFn = (value?: any) => any; export class Promise { /* 三种状态 */ private readonly PENDING: string = 'pending'; private readonly RESOLVED: string = 'resolved'; private readonly REJECTED: string = 'rejected'; /* promise当前状态 */ private _status: string; /* promise执行结果 */ private _value: string; /* 成功的回调 */ private _resolvedCallback: Function[] = []; /* 失败的回调 */ private _rejectedCallback: Function[] = []; /** * 处理 resolve 的状态变更相关工作,参数接收外部传入的执行结果 */ private _handleResolve(value?: any) {} /** * 处理 reject 的状态变更相关工作,参数接收外部传入的失败原因 */ private _handleReject(value?: any) {} /** * 构造函数,接收一个 task 处理函数,task 有两个可选参数,类型也是函数,其实也就是上面的两个处理状态变更工作的函数(_handleResolve,_handleReject),用来给使用者来触发状态变更使用 */ constructor(task: (resolve?: statusChangeFn, reject?: statusChangeFn) => void) {} /** * then 方法,接收两个可选参数,用于注册成功或失败时的回调处理,所以类型也是函数,函数有一个参数,接收 Promise 执行结果或失败原因,同时可返回任意值,作为新 Promise 的执行结果 */ then(onResolved?: callbackFn, onRejected?: callbackFn): Promise { return null; } catch(onRejected?: callbackFn): Promise { return this.then(null, onRejected); } }