JavaScript Promise 实现(二)

在前文中,我们已经搭建好了 Promise 的一个基本“外形”。

var Promise = function (executor) {
    this._state = null;
    // ToDo: Implement it.
};
Promise.prototype.then = function (onfulfilled, onrejected) {
    return Promise(function (resolve, reject) {
        // ToDo: Implement it.
    });
};
Promise.prototype.catch(onrejected) {
    return this.then(null, onrejected);
}
现在,我们要开始具体实现这些待实现的代码了。

由于在构造函数中,传入的主要业务逻辑函数中,需要传入两个参数,即成功和失败的触发器,因此,我们先实现这两个触发器。那么,成功触发器里需要做一些什么呢?

  1. 检查状态。如果当前不处于等待状态,则不做任何事情。
  2. 检查传入的参数是不是该 Promise 自己,是的话是不支持的。
  3. 将状态设置为执行成功,同时保存运算结果。
  4. 唤起所有注册的成功事件。
  5. 我们需要通过 try-catch 方式,将此时出错的情形,引导至失败的处理方式中去。
于是,我们有了一下代码。
Promise.prototype._resolve = function (value) {
    if (this._state !== null) return;
    if (value === this) throw new TypeError('A promise cannot be resolved with itself.');
 
    this._state = true;
    this._value = value as T;
 
    // ToDo: 唤起所有回调。
}

当然,刚才说过,需要一个 try-catch 将里面的代码包起来。

Promise.prototype._resolve(value) {
    try {
        // 刚才里面的代码。
    } catch (e) {
        // ToDo: 失败响应。
    }
}

可是,你可能会意识到,如果传入的参数,其实是一个其它的 Promise 对象怎么办?显然我们也计划支持这种场景,即此时应当自动将下一个返回的 Promise 对象用传入的代替,因此我们先需要有一个参数将这种情形记录下来,以便之后检查。我们需要在构造函数里再加入一个属性。

this._delegating = false;

然后,在刚才的 _resolve 方法里,在设置状态之前,以及校验传入参数是否自己之后,加上以下代码。

if (value && (typeof value === 'object' || typeof value === 'function') && typeof (value.then === "function") {
    this._delegating = true;
    value.then(this._resolve, this._reject);
    return;
}

由此,成功状态的判断和前置处理就完成了。那么,我们继续实现失败响应。其实和成功响应差不多,无非就是先判断当前状态是否为等待中,如果是的话,则将状态更新为失败,并记录出错信息,然后唤起所有回调。

Promise.prototype._reject = function (reason) {
    if (this._state !== null) return
    this._state = false;
    this._value = reason;
 
    // ToDo: 唤起所有回调。
}

在这里,我们都预留了唤起所有回调没有实现。要实现这个,我们需要先找个地方记录这些回调及相关信息。由于每组回调都可以包含成功和失败之后的处理,并且都可以有返回值做为下一个 Promise 的结果,因此,针对每组回调,我们都需要保存以下信息。

  • 成功时的回调。
  • 失败时的回调。
  • 成功时,下一个 Promise 对象的处理过程。
  • 失败时,下一个 Promise 对象的处理过程。

我们需要在构造函数里放一个数组来存储所有的这些信息。

this._deferreds = [];

然后,针对每一组这个信息,我们需要一个通用处理方法,能够在当下不同状态进行不同的处理:主要业务逻辑结束前进行记录;以及,在结束后进行执行,但考虑到这时延迟执行的,因此结束后需要等待当前线程正在运作的事务执行完后,才开始执行,故需要使用一个回调。

Promise.prototype._handle = function (deferred) {
    if (this._state === null) {
        this._deferreds.push(deferred);
        return;
    }
 
    setTimeout(() => {
        // ToDo: 结束后执行的回调。
    }, 0);
}

对于此处的结束后执行的回调,我们需要做这些事情。

  1. 检查状态,以确定到底是执行成功的回调还是失败的回调。
  2. 如果没有对应的回调,则直接执行当前的成功或失败的路由。
  3. 在 try-catch 包内,触发对应的回调,若失败,则转入失败处理。

代码如下。

// 根据状态获取对应的回调。
var cb = this._state ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
    // 如果并无对应的回调,则使用当前默认的回调。
    (this._state ? deferred.resolve : deferred.reject)(this._value as any);
    return;
}
 
// 唤起注册的回调。
var ret: TResult | PromiseLike<TResult>;
try {
    ret = cb(this._value) as any;
}
catch (e) {
    deferred.reject(e);
    return
}
 
// 成功执行。
deferred.resolve(ret);

现在,我们已经能够处理单组注册事件了。在前面声明的 then 方法中,我们也需要调用这个方法。如下代码。

Promise.prototype.then = function (onfulfilled, onrejected) {
    return Promise(function (resolve, reject) {
        this._handle<TResult>({
            onFulfilled: onfulfilled,
            onRejected: onrejected,
            resolve: resolve,
            reject: reject
        });
    });
};

好。现在回到刚才我们还没实现的唤起所有回调。我们只需写一个函数,里面放一个循环,就能轻松搞定。

Promise.prototype._finale = function () {
    for (var i = 0, len = this._deferreds.length; i < len; i++)
        this._handle(this._deferreds[i]);
    this._deferreds = null;
}

于是,刚才那些唤起所有回调的部分,只需调用上面这个函数即可。剩下的事情就是把构造函数完成了。

try {
    executor((value) => {
        if (!this._delegating) this._resolve(value)
    }, (reason) => {
        if (!this._delegating) this._reject(reason)
    });
} catch (e) {
    if (!this._delegating) this._reject(e)
}

现在,Promise 初步完成了。当然,由于这里是为了说清楚原理,大量使用 Prototype 来定义一些方法;但,或许你想隐藏一些私有的成员属性和方法,那么你可以依此进行一些改造,例如把许多东西都放入构造函数内实现,这里就不再继续介绍了。那么,接下来还有什么呢?其实,Promise 还有一些辅助方法可以帮助我们处理一些多任务的事情,后面的文章会以其中一个例子,来解释这是如何实现的。

【未完待续】

文章类型及复杂度:Web 前端开发进阶。

节选翻译自 MSDN 博文 JavaScript Promise,内容有所调整。

http://blogs.msdn.com/b/kingcean/archive/2016/03/25/promise-in-web.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值