代码区项目交易流程

Promise进阶――如何实现一个Promise库


从上次更新Promise/A+规范后,已经很久没有更新博客了。之前由于业务需要,完成了一个TypeScript语言的Promise库。这次我们来和大家一步一步介绍下,我们如何实现一个符合Promise/A+规范的Promise库。

如果对Promise/A+规范还不太了解的同学,建议先看看上一篇博客―― [译]前端基础知识储备――Promise/A+规范 。 实现流程

首先,我们来看下,在我实现的这一个Promise中,代码由下面这几部分组成:

全局异步函数执行器 常量与属性 类方法 类静态方法

通过上面这四个部分,我们就能够得到一个完整的Promise。这四个部分互相有关联,接下来我们一个一个模块来看。

全局异步函数执行器

在之前的Promiz的源码分析的博客中我有提到过,我们如何来实现一个异步函数执行器。通过javascript的执行原理我们可以知道,如果要实现异步执行相关函数的话,我们可以选择使用宏任务和微任务,这一点在Promise/A+的规范中也有提及。因此,下面我们提供了一个用宏任务来实现异步函数执行器的代码供大家参考。

let index = 0; if (global.postMessage) { global.addEventListener('message', (e) => { if (e.source === global) { let id = e.data; if (isRunningTask) { nextTick(functionStorage[id]); } else { isRunningTask = true; try { functionStorage[id](); } catch (e) { } isRunningTask = false; } delete functionStorage[id]; functionStorage[id] = void 0; } }); } function nextTick(func) { if (global.setImmediate) { global.setImmediate(func); } else if (global.postMessage) { functionStorage[++index] = func; global.postMessage(index, '*') } else { setTimeout(func); } } 复制代码

通过上面的代码我们可以看到,我们一共使用了 setImmediate 、 postMessage 、 setTimeout 这三个添加宏任务的方法来进行一步函数执行。

常量与属性

说完了如何进行异步函数的执行,我们来看下相关的常量与属性。在实现Promise之前,我们需要定义一些常量和类属性,用于后面存储数据。让我们一个一个来看下。

常量

首先,Promise共有5个状态,我们需要用常量来进行定义,具体如下:

enum State { pending = 0, resolving = 1, rejecting = 2, resolved = 3, rejected = 4 }; 复制代码

这五个常量分别对应Promise中的5个状态,相信大家能够从名字中理解,我们就不多讲了。

属性

在Promise中,我们需要一些属性来存储数据状态和后续的Promise引用,具体如下:

class Promise { private _value; private _reason; private _next = []; public state: State = 0; public fn; public er; } 复制代码

我们对属性进行逐一说明:

_value ,表示在resolved状态时,用来存储当前的值。 _reason ,表示在rejected状态时,用来存储当前的原因。 _next ,表示当前Promise后面跟着 then 函数的引用。 fn ,表示当前Promise中的 then 方法的第一个回调函数。 er ,表示当前Promise中的 then 方法的的第二个回调函数(即 catch 的第一个参数,下面看 catch 实现方法就能理解)。 类方法

看完了常量与类的属性,我们来看下类的静态方法。

Constructor

首先,如果我们要实现一个Promise,我们需要一个构造函数来初始化最初的Promise。具体代码如下:

class Promise { constructor(resolver?) { if (typeof resolver !== 'function' && resolver !== undefined) { throw TypeError() } if (typeof this !== 'object') { throw TypeError() } try { if (typeof resolver === 'function') { resolver(this.resolve.bind(this), this.reject.bind(this)); } } catch (e) { this.reject(e); } } } 复制代码

从Promise/A+的规范来看,我们可以知道,如果 resolver 存在并且不是一个function的话,那么我们就应该抛出一个错误;否则,我们应该将 resolve 和 reject 方法传给 resolver 作为参数。

resolve && reject

那么, resolve 和 reject 方法又是做什么的呢?这两个方法主要是用来让当前的这个Promise转换状态的,即从 pending 状态转换为 resolving 或者 rejecting 状态。下面让我们来具体看下代码:

class Promise { resolve(value) { if (this.state === State.pending) { this._value = value; this.state = State.resolving; nextTick(this._handleNextTick.bind(this)); } return this; } reject(reason) { if (this.state === State.pending) { this._reason = reason; this.state = State.rejecting; this._value = void 0; nextTick(this._handleNextTick.bind(this)); } return this; } } 复制代码

从上面的代码中我们可以看到,当 resolve 或者 reject 方法被触发时,我们都改变了当前这个Proimse的状态,并且异步调用执行了 _handleNextTick 方法。状态的改变标志着当前的Promise已经从 pending 状态改变成了 resolving 或者 rejecting 状态,而相应 _value 和 _reson 也表示上一个Promise传递给下一个Promise的数据。

那么,这个 _handleNextTick 方法又是做什么的呢?其实,这个方法的作用很简单,就是用来处理当前这个Promise后面跟着的 then 函数传递进来的回调函数 fn 和 er 。

then && catch

在了解 _handleNextTick 之前,我们们先看下 then 函数和 catch 函数的实现。

class Promise { public then(fn, er?) { let promise = new Promise(); promise.fn = fn; promise.er = er; if (this.state === State.resolved) { promise.resolve(this._value); } else if (this.state === State.rejected) { promise.reject(this._reason); } else { this._next.push(promise); } return promise; } public catch(er) { return this.then(null, er); } } 复制代码

因为 catch 函数调用就是一个 then 函数的别名,我们下面就只讨论 then 函数。

在 then 函数执行时,我们会创建一个新的Promise,然后将传入的两个回调函数用新的Promise的属性保存下来。然后,先判断当前的Promise的状态,如果已经是 resolved 或者 rejected 状态时,我们立即调用新的Promise中 resolve 或者 reject 方法,让下将当前Promise的 _value 或者 _reason 传递给下一个Promise,并且触发下一个Promise的状态改变。如果当前Promise的状态仍然为 pending 时,那么就将这个新生成的Promise保存下来,等当前这个Promise的状态改变后,再触发新的Promise变化。最后,我们返回了这个Promise的实例。

handleNextTick

看完了 then 函数,我们就可以来看下我们提到过的 handleNextTick 函数。

class Promise { private _handleNextTick() { try { if (this.state === State.resolving && typeof this.fn === 'function') { this._value = this.fn.call(getThis(), this._value); } else if (this.state === State.rejecting && typeof this.er === 'function') { this._value = this.er.call(getThis(), this._reason); this.state = 1; } } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._finishThisTypeScriptPromise(); } // if promise === x, use TypeError to reject promise // 如果promise和x指向同一个对象,那么用TypeError作为原因拒绝promise if (this._value === this) { this.state = State.rejecting; this._reason = new TypeError(); this._value = void 0; } this._finishThisTypeScriptPromise(); } } 复制代码

我们先来看一个简单版的 _handleNextTick 函数,这样能够帮助我们快速理解Promise主流程。

异步触发了 _handleNextTick 函数后,我们会判断当前用户处于的状态,如果当前Promise是 resolving 状态,我们就会调用 fn 函数,即我们在 then 函数调用时给新的Promise设置的那个 fn 函数;而如过当前Promise是 rejecting 状态,我们就会调用 er 函数。

上面提到的 getThis 方法是用来获取特定的this值,具体的规范要求我们将在稍后再进行介绍。

通过执行这两个同步的 fn 或 er 函数,我们能够得到当前Promise执行完传入回调后的值。在这里需要说明的是:我们在执行 fn 或者 er 函数之前,我们在 _value 和 _reason 中存放的值,是上一个Promise传递下来的值。只有当执行完了 fn 或者 er 函数后, _value 和 _reason 中存放的值才是我们需要传递给下一个Promise的值。

大家到这里可能会奇怪,我们的 this 指向没有发生变化,但是为什么我们的 this 指向的是那个新的Promise,而不是原来的那个Promise呢?

我们可以从另外一个角度来看待这个问题:我们当

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

点击收藏

LAST 奇技淫巧 - Vue Mixins 高级组件 与 Vue HOC 高阶组件 实践 Reading keywords from the jQuery text box NEXT