注意:骨架这里的代码,我用了 TypeScript,这是一种强类型语言,可以标明各个变量、参数类型,便于讲述和理解,看不懂没关系,下面有编译成 js 版的
所以,我们要补充完成的其实就是三部分:Promise 构造函数都做了哪些事、状态变更需要做什么处理、then 注册回调函数时需要做的处理
第二步:构造函数Promise 的构造函数做的事,其实很简单,就是马上执行传入的 task 处理函数,并将自己内部提供的两个状态变更处理的函数传递给 task,同时将当前 promise 状态置为 PENDING(执行中)
constructor(task) { // 1. 将当前状态置为 PENDING this._status = this.PENDING; // 参数类型校验 if (!(task instanceof Function)) { throw new TypeError(`${task} is not a function`); } try { // 2. 调用 task 处理函数,并将状态变更通知的函数传递过去,需要注意 this 的处理 task(this._handleResolve.bind(this), this._handleReject.bind(this)); } catch (e) { // 3. 如果 task 处理函数发生异常,当做失败来处理 this._handleReject(e); } } 第三步:状态变更Promise 状态变更的相关处理是我觉得实现 Promise 最难的一部分,这里说的难并不是说代码有多复杂,而是说这块需要理解透,或者看懂规范并不大容易,因为需要考虑一些处理,网上看了些 Promise 实现的文章,这部分都存在问题
状态变更的工作,是由传给 task 处理函数的两个函数参数被调用时触发进行,如:
new Promise((resolve, reject) => { resolve(1); });resolve 或 reject 的调用,就会触发 Promise 内部去处理状态变更的相关工作,还记得构造函数做的事吧,这里的 resolve 或 reject 其实就是对应着内部的 _handleResolve 和 _handleReject 这两个处理状态变更工作的函数
但这里有一点需要注意,是不是 resolve 一调用,Promise 的状态就一定发生变化了呢?
答案不是的,网上看了些这类文章,他们的处理是 resolve 调用,状态就变化,就去处理回调队列了
但实际上,这样是错的
状态的变更,其实依赖于 resolve 调用时,传递过去的参数的类型,因为这里可以传递任意类型的值,可以是基本类型,也可以是 Promise
当类型不一样时,对于状态的变更处理是不一样的,开头那篇规范里面有详细的说明,但要看懂并不大容易,我这里就简单用我的理解来讲讲:
resolve(x) 触发的 pending => resolved 的处理:
当 x 类型是 Promise 对象时:
当 x 这个 Promise 的状态变化结束时,再以 x 这个 Promise 内部状态和结果(_status 和 _value)作为当前 Promise 的状态和结果进行状态变更处理
可以简单理解成当前的 Promise 是依赖于 x 这个 Promise 的,即 x.then(this._handleResolve, this._handleReject)
当 x 类型是 thenable 对象(具有 then 方法的对象)时:
把这个 then 方法作为 task 处理函数来处理,这样就又回到第一步即等待状态变更的触发
可以简单理解成 x.then(this._handleResolve, this._handleReject)
这里的 x.then 并不是 Promise 的 then 处理,只是简单的一个函数调用,只是刚好函数名叫做 then
其余类型时:
内部状态(_status)置为 RESOLVE
内部结果(_value)置为 x
模拟创建微任务(setTimeout)处理回调函数队列
reject(x) 触发的 pending => rejected 的处理:
不区分 x 类型,直接走 rejected 的处理
内部状态(_status)置为 REJECTED
内部结构(_value)置为 x
模拟创建微任务(setTimeout)处理回调函数队列