【问题标题】:Basic Javascript promise implementation attempt基本的 Javascript 承诺实现尝试
【发布时间】:2014-07-09 11:34:53
【问题描述】:

为了更好地理解 Promise 在 Javascript 中的工作原理,我决定尝试一下并自己编写基本实现代码。

基本上我想实现将函数作为参数的 Promises 对象(我在我的代码中称之为 Aaa)。这个函数可以调用resolve到resolve这个promise,或者reject到reject它。基本实现和用法如下。不确定根据 Promise 规范,第二个参数是否可以接受,但这就是我目前所得到的。

Aaa=function(f,pause) { 

    console.log("ggg");

    var t=this;
    this.f=f;
    this.thens=[];

    this.resolve=function(g) {

        for(var i=0;i<t.thens.length;i++)
        {
            // try/catch to be used later for dealing with exceptions

            try
            {
                t.thens[i].f(g);
                t.thens[i].resolve();
            }   
            catch(ex)
            {}

        }
    };  

    // to be implemented later
    this.reject=function(g) {};

    this.then=function(resolve,reject) {

        // i'm passing true for pause argument as we dont need to execute promise code just yet
        var nextPromise=new Aaa(resolve,true);

        this.thens.push(nextPromise);

        return nextPromise;
    }


    if(!pause)
        this.f(this.resolve,this.reject); 

}


var aaa=new Aaa(function(resolve,reject) {

    console.log("aaa");

    setTimeout(function() {

        console.log("fff");
        resolve("good");

    },2000);

    console.log("bbb");

});

所以现在可以创建、调用和解析 Promise。每个then 方法都将返回新的Aaa(Promise),因此可以将它们链接起来。现在下面的代码使用了上面创建的 Promise 并链接了 then 回调。每个 then 返回新的承诺,在这种情况下,它似乎工作正常:

aaa.then(function(res) {

    console.log("ccc");
    console.log(res);

})
.then(function(res) {
    console.log("ddd");
    console.log(res);
},function(rej) {
    console.log("eee");
    console.log(rej);
});

我得到的输出是:

ggg
aaa 
bbb 
ggg 
ggg 
fff 
ccc 
good 
ddd 
undefined 

但问题是当then 调用之一返回一个承诺时:

aaa.then(function(res) {

    console.log("ccc");
    console.log(res);

    // here we return the promise manually. then next then call where "ddd" is output should not be called UNTIL this promise is resolved. How to do that?

        return new Aaa(function(resolve,reject) {

        console.log("iii");

        setTimeout(function() {
        console.log("kkk");
            resolve("good2");
            // reject("bad");

        },2000);

        console.log("jjj");

    }).then(function (res) {
        console.log("lll");

        console.log(res);
    });

})
.then(function(res) {
    console.log("ddd");
    console.log(res);
},function(rej) {
    console.log("eee");
    console.log(rej);
});

输出是:

ggg 
aaa 
bbb 
ggg 
ggg  
fff  
ccc  
good  
ggg  
iii  
jjj  
ggg  
ddd  
undefined  
kkk  
lll  
good2 

在我们刚刚添加的返回承诺得到解决之前,不应调用输出 ddd 的调用。

如何最好地实施?

【问题讨论】:

标签: javascript promise


【解决方案1】:

(对于完整的 Promise 实现,请向下滚动)。

代码中的一些问题

有几个问题,但我认为您的代码中的主要错误是您将提供给then 方法的参数作为参数传递给promise 构造函数:

this.then=function(resolve,reject) {
    var nextPromise=new Aaa(resolve,true);
    // ...

虽然这两个参数都是回调函数,但它们具有不同的签名,并且服务于完全不同的目的:

  • promise 构造函数的参数是一个回调函数,立即同步执行。 函数作为第一个参数传递给它,你可以用它来解决你正在创建的承诺。
  • then 方法的(第一个)参数是一个回调函数,它只会在以后异步执行,当基本承诺被解析时,解析的值作为参数传递。

您也可以在代码中看到不同之处,您可以将构造函数的参数存储为 f 属性。你有这两个:

t.thens[i].f(g);

...其中 g 是解析后的值,但也是这样:

this.f(this.resolve,this.reject); 

...参数是函数。当您创建 nextPromise 时,实际上您将首先使用这两个参数调用 f,然后再使用 g 参数。

完全符合 Promises/A+ 的实现

我们可以按照Promises/A+ specification 中的要求构建自己的 Promise 实现:

2.1 承诺状态

只允许 2 种状态转换:从挂起到完成,以及从挂起到拒绝。任何其他转换都不可能发生,并且一旦执行转换,承诺值(或拒绝原因)不应更改。

这是一个简单的实现,它遵守上述限制。 cmets引用了上述规范中的编号要求:

function MyPromise(executor) {
    this.state = 'pending';
    this.value = undefined;
    executor(this.resolve.bind(this), this.reject.bind(this));
}

// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.resolve = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
}

MyPromise.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
}

当然,这里不提供then方法,这是Promises的关键:

2.2 then 方法

这是规范的核心。上面的代码可以扩展以公开then 方法,该方法返回一个promise 并提供适当的then 回调的异步执行,仅一次,提供多个then 调用,将异常转换为拒绝,...等.

所以下面的代码添加了then方法,还添加了一个单独定义的broadcast函数,因为它必须在任何状态变化时调用:这不仅包括then方法的效果(一个承诺被添加到一个列表中),还有resolvereject 方法(状态和值的变化)。

function MyPromise(executor) {
    this.state = 'pending';
    this.value = undefined;
    // A list of "clients" that need to be notified when a state
    //   change event occurs. These event-consumers are the promises
    //   that are returned by the calls to the `then` method.
    this.consumers = [];
    executor(this.resolve.bind(this), this.reject.bind(this));
}

// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.resolve = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
    this.broadcast();
}    

MyPromise.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
    this.broadcast();
}    

// A promise’s then method accepts two arguments:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    var consumer = new MyPromise(function () {});
    // 2.2.1.1 ignore onFulfilled if not a function
    consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // 2.2.1.2 ignore onRejected if not a function
    consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
    this.consumers.push(consumer);
    // It might be that the promise was already resolved... 
    this.broadcast();
    // 2.2.7: .then() must return a promise
    return consumer;
};

MyPromise.prototype.broadcast = function() {
    var promise = this;
    // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
    if (this.state === 'pending') return;
    // 2.2.6.1, 2.2.6.2 all respective callbacks must execute
    var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
    var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
    // 2.2.4 onFulfilled/onRejected must be called asynchronously
    setTimeout(function() {
        // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
        promise.consumers.splice(0).forEach(function(consumer) {
            try {
                var callback = consumer[callbackName];
                // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
                // 2.2.5 call callback as plain function without context
                if (callback) {
                    // TODO: 2.2.7.1. For now we simply fulfill the promise:
                    consumer.resolve(callback(promise.value)); 
                } else {
                    // 2.2.7.3 resolve in same way as current promise
                    consumer[resolver](promise.value);
                }
            } catch (e) {
                // 2.2.7.2
                consumer.reject(e);
            };
        })
    });
};

这几乎涵盖了所有内容,除了在TODO: 注释处,必须调用所谓的 Promise Resolution Procedure:

2.3 承诺解决程序

这是一个以不同方式处理 thenable(甚至是 promise)值的过程:该过程不会按原样返回值,而是对该值执行 then 方法,并使用接收到的值异步履行承诺来自then 回调。规范中没有提到它,但不仅在 then 方法中执行,而且在使用这样的值解决主要承诺时执行此操作很有趣。

所以现有的resolve 方法应该替换为这个“Promise Resolution Procedure”,它会调用原来的方法。原始的可以称为“履行”,以表明它将始终履行承诺:

function MyPromise(executor) {
    this.state = 'pending';
    this.value = undefined;
    // A list of "clients" that need to be notified when a state
    //   change event occurs. These event-consumers are the promises
    //   that are returned by the calls to the `then` method.
    this.consumers = [];
    executor(this.resolve.bind(this), this.reject.bind(this));
}

// 2.1.1.1: provide only two ways to transition
MyPromise.prototype.fulfill = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
    this.broadcast();
}    

MyPromise.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
    this.broadcast();
}    

// A promise’s then method accepts two arguments:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    var consumer = new MyPromise(function () {});
    // 2.2.1.1 ignore onFulfilled if not a function
    consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // 2.2.1.2 ignore onRejected if not a function
    consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
    this.consumers.push(consumer);
    // It might be that the promise was already resolved... 
    this.broadcast();
    // 2.2.7: .then() must return a promise
    return consumer;
};

MyPromise.prototype.broadcast = function() {
    var promise = this;
    // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
    if (this.state === 'pending') return;
    // 2.2.6.1, 2.2.6.2 all respective callbacks must execute
    var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
    var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
    // 2.2.4 onFulfilled/onRejected must be called asynchronously
    setTimeout(function() {
        // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
        promise.consumers.splice(0).forEach(function(consumer) {
            try {
                var callback = consumer[callbackName];
                // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
                // 2.2.5 call callback as plain function without context
                if (callback) {
                    // 2.2.7.1. execute the Promise Resolution Procedure:
                    consumer.resolve(callback(promise.value)); 
                } else {
                    // 2.2.7.3 resolve in same way as current promise
                    consumer[resolver](promise.value);
                }
            } catch (e) {
                // 2.2.7.2
                consumer.reject(e);
            };
        })
    });
};

// The Promise Resolution Procedure: will treat values that are thenables/promises
// and will eventually call either fulfill or reject/throw.
MyPromise.prototype.resolve = function(x) {
    var wasCalled, then;
    // 2.3.1
    if (this === x) {
        throw new TypeError('Circular reference: promise value is promise itself');
    }
    // 2.3.2
    if (x instanceof MyPromise) {
        // 2.3.2.1, 2.3.2.2, 2.3.2.3
        x.then(this.resolve.bind(this), this.reject.bind(this));
    } else if (x === Object(x)) { // 2.3.3
        try {
            // 2.3.3.1
            then = x.then;
            if (typeof then === 'function') {
                // 2.3.3.3
                then.call(x, function resolve(y) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.1 recurse
                    this.resolve(y);
                }.bind(this), function reject(reasonY) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.2
                    this.reject(reasonY);
                }.bind(this));
            } else {
                // 2.3.3.4
                this.fulfill(x);
            }
        } catch(e) {
            // 2.3.3.3.4.1 ignore if call was made
            if (wasCalled) return;
            // 2.3.3.2 or 2.3.3.3.4.2
            this.reject(e);
        }
    } else {
        // 2.3.4
        this.fulfill(x);
    }
}

这现在符合 Promises/A+,至少它通过了测试套件。然而,Promise 对象暴露了太多的方法和属性:

只有then 的 Promise 对象

上面构建的构造函数创建的东西更像一个 Deferred 对象,即公开resolvereject 方法。更糟糕的是,statusvalue 属性是可写的。因此,将 this 视为不安全的 Deferred 对象的构造函数会更合乎逻辑,并在此基础上创建一个单独的 Promise 构造函数,但只公开需要的内容:then 方法和可以访问 @ 的构造函数回调987654351@和reject

延迟对象可以不使用构造函数回调参数,并通过 promise 属性提供对纯 Promise 对象的访问:

function Deferred() {
    this.state = 'pending';
    this.value = undefined;
    this.consumers = [];
    this.promise = Object.create(MyPromise.prototype, {
        then: { value: this.then.bind(this) }
    });
}

// 2.1.1.1: provide only two ways to transition
Deferred.prototype.fulfill = function (value) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'fulfilled'; // 2.1.1.1: can transition
    this.value = value; // 2.1.2.2: must have a value
    this.broadcast();
}    

Deferred.prototype.reject = function (reason) {
    if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore
    this.state = 'rejected'; // 2.1.1.1: can transition
    this.value = reason; // 2.1.3.2: must have a reason
    this.broadcast();
}    

// A promise’s then method accepts two arguments:
Deferred.prototype.then = function(onFulfilled, onRejected) {
    var consumer = new Deferred();
    // 2.2.1.1 ignore onFulfilled if not a function
    consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
    // 2.2.1.2 ignore onRejected if not a function
    consumer.onRejected = typeof onRejected === 'function' ? onRejected : null;
    // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise
    this.consumers.push(consumer);
    // It might be that the promise was already resolved... 
    this.broadcast();
    // 2.2.7: .then() must return a promise
    return consumer.promise;
};

Deferred.prototype.broadcast = function() {
    var promise = this;
    // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved
    if (this.state === 'pending') return;
    // 2.2.6.1, 2.2.6.2 all respective callbacks must execute
    var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected';
    var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject';
    // 2.2.4 onFulfilled/onRejected must be called asynchronously
    setTimeout(function() {
        // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once
        promise.consumers.splice(0).forEach(function(consumer) {
            try {
                var callback = consumer[callbackName];
                // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else
                // 2.2.5 call callback as plain function without context
                if (callback) {
                    // 2.2.7.1. execute the Promise Resolution Procedure:
                    consumer.resolve(callback(promise.value)); 
                } else {
                    // 2.2.7.3 resolve in same way as current promise
                    consumer[resolver](promise.value);
                }
            } catch (e) {
                // 2.2.7.2
                consumer.reject(e);
            };
        })
    });
};

// The Promise Resolution Procedure: will treat values that are thenables/promises
// and will eventually call either fulfill or reject/throw.
Deferred.prototype.resolve = function(x) {
    var wasCalled, then;
    // 2.3.1
    if (this.promise === x) {
        throw new TypeError('Circular reference: promise value is promise itself');
    }
    // 2.3.2
    if (x instanceof MyPromise) {
        // 2.3.2.1, 2.3.2.2, 2.3.2.3
        x.then(this.resolve.bind(this), this.reject.bind(this));
    } else if (x === Object(x)) { // 2.3.3
        try {
            // 2.3.3.1
            then = x.then;
            if (typeof then === 'function') {
                // 2.3.3.3
                then.call(x, function resolve(y) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.1 recurse
                    this.resolve(y);
                }.bind(this), function reject(reasonY) {
                    // 2.3.3.3.3 don't allow multiple calls
                    if (wasCalled) return;
                    wasCalled = true;
                    // 2.3.3.3.2
                    this.reject(reasonY);
                }.bind(this));
            } else {
                // 2.3.3.4
                this.fulfill(x);
            }
        } catch(e) {
            // 2.3.3.3.4.1 ignore if call was made
            if (wasCalled) return;
            // 2.3.3.2 or 2.3.3.3.4.2
            this.reject(e);
        }
    } else {
        // 2.3.4
        this.fulfill(x);
    }
}

function MyPromise(executor) {
    // A Promise is just a wrapper around a Deferred, exposing only the `then`
    // method, while `resolve` and `reject` are available in the constructor callback
    var df = new Deferred();
    // Provide access to the `resolve` and `reject` methods via the callback
    executor(df.resolve.bind(df), df.reject.bind(df));
    return df.promise;
}

可以对这段代码进行多种优化,例如将 Deferred 方法设为私有函数,以及将相似的代码合并到更短的代码块中,但就目前而言,它非常清楚地显示了每个需求的覆盖位置。

编码愉快。

【讨论】:

  • 迄今为止我见过的对 promise 实现的最佳解释。
  • 能否解释一下广播功能和消费者的目的?
  • @bikashamit,在我的答案和代码 cmets 中已经有一些解释。简单来说:消费者是由thencatch 调用promise 对象返回的promise。这些需要知道何时调用作为参数提供给thencatch 的回调函数。当 promise 解决时,需要调用适当的回调。 broadcast 函数负责通知这些消费者,以便他们负责进行调用并采取行动。
  • 这条线是做什么用的? handlers.forEach(handle) 似乎只是循环遍历每个句柄而不做任何事情
  • @lolcatapril24,该行没有出现在我的代码中。我认为您的评论是关于另一个答案。但无论如何:forEach 将为数组 (handlers) 的每个条目调用给定函数 (handle),并将数组元素作为参数传递给它。
【解决方案2】:

我尝试用 ES6 来实现它。发布,因为它可能对其他人有用

class MyPromise {
  _value = null;
  _isRejected = false;
  _fullFilled = false;
  _handlers = [];
  _errorHandlers = [];
  _error = null;

  constructor(func) {
    func(this._resolve, this._reject);
  }

  _resolve = (value) => {
    this._value = value;
    this._fullFilled = true;
    this._handlers.forEach(handler => handler(value));
  };

  _reject = (error) => {
    this._isRejected = true;
    this._error = error;
    this._errorHandlers.forEach(errorHandler => errorHandler(error));
  };

  catch(errorHandler){
    return new MyPromise((resolve, reject) => {
      this._errorHandler(resolve, reject, errorHandler)
    })
  }

  _errorHandler(resolve, reject, callback){
    const runErrorHandler = () => {
      let error;
      let returnedFromCatchCallback;
      try{
        returnedFromCatchCallback = callback(this._error);
      }catch(_error){
        error = _error;
        reject(error);
      }
      resolve(returnedFromCatchCallback);
    };

    if(this._isRejected){
      runErrorHandler(this._error);
    }

    this._errorHandlers.push(runErrorHandler);
  }

  then(handler, errorHandler) {
    const returnOfHandler = new MyPromise((resolve, reject) => {
      const runHandler = (value) => {
        try{
          resolve(handler(value));
        }catch(error){
          reject(error);
        }
      };
      this._handlers.push(runHandler);

      if(this._fullFilled) {
        runHandler(this._value);
      }

      this._errorHandler(resolve, reject, errorHandler);
    });

    return returnOfHandler;
  }
}

export default MyPromise;

【讨论】:

  • 一些问题:这可以多次解决(甚至被满足和拒绝 - 更糟糕的是,_errorHandler 甚至这样做),resolve 不是递归处理 thenables,then不处理省略的回调,它会通过持有不再需要的回调来泄漏内存。
  • @Bergi 是的,我知道这有多个问题。我打算再试一次。感谢您的反馈。
【解决方案3】:

有许多案件您没有在这里处理。最好的办法是从将 Promise 构建为状态机开始:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];
}

现在让我们定义一个简单的帮助器,以便在我们的其余实现中使用:

// a function that returns `then` if `value` is a promise, otherwise `null`
function getThen(value) {
  if (value && (typeof value === 'object' || typeof value === 'function')) {
    var then = value.then;
    if (typeof then === 'function') {
      return then;
    }
  }
  return null;
}

接下来,我们需要考虑可能发生的每一种转换:

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }
}

注意resolve 是如何接收一个 Promise 作为它的参数的,但是一个 Promise 永远不能用另一个 Promise 来实现。所以我们必须处理这种特殊情况。

另请注意,Promise 只能被履行/拒绝一次。我们还存在第三方 Promise 可能行为不端的问题,我们应该保护我们的代码免受这种情况的影响。出于这个原因,我不只是从resolve 中调用result.then(resolve, reject)。相反,我将其拆分为一个单独的函数:

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 */
function doResolve(fn, onFulfilled, onRejected) {
  var done = false;
  try {
    fn(function (value) {
      if (done) return
      done = true
      onFulfilled(value)
    }, function (reason) {
      if (done) return
      done = true
      onRejected(reason)
    })
  } catch (ex) {
    if (done) return
    done = true
    onRejected(ex)
  }
}

所以现在我们有了一个完整的状态机,但是没有办法观察或触发状态的变化。让我们首先添加一种通过传入解析器函数来触发状态更改的方法。

function Promise(fn) {
  if (typeof this !== 'object')
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function')
    throw new TypeError('fn must be a function');

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
  }

  doResolve(fn, resolve, reject);
}

如您所见,我们重复使用 doResolve,因为我们还有另一个不受信任的解析器。 fn 可能会多次调用resolvereject,它可能会抛出错误。我们需要处理所有这些情况(这就是doResolve 所做的)。

我们现在有了完整的状态机,但我们还没有公开任何关于它处于什么状态的信息。让我们尝试添加一个类似于.then.done(onFulfilled, onRejected) 方法,只是它不返回一个 Promise 和不处理onFulfilledonRejected 抛出的错误。

var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise(fn) {
  if (typeof this !== 'object')
    throw new TypeError('Promises must be constructed via new');
  if (typeof fn !== 'function')
    throw new TypeError('fn must be a function');

  // store state which can be PENDING, FULFILLED or REJECTED
  var state = PENDING;

  // store value once FULFILLED or REJECTED
  var value = null;

  // store sucess & failure handlers
  var handlers = [];

  function resolve(result) {
    try {
      var then = getThen(result);
      if (then) {
        doResolve(then.bind(result), resolve, reject)
        return
      }
      state = FULFILLED;
      value = result;
      handlers.forEach(handle);
      handlers = null;
    } catch (e) {
      reject(e);
    }
  }

  function reject(error) {
    state = REJECTED;
    value = error;
    handlers.forEach(handle);
    handlers = null;
  }

  function handle(handler) {
    if (state === PENDING) {
      handlers.push(handler);
    } else {
      if (state === FULFILLED && typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value);
      }
      if (state === REJECTED && typeof handler.onRejected === 'function') {
        handler.onRejected(value);
      }
    }
  }
  this.done = function (onFulfilled, onRejected) {
    setTimeout(function () { // ensure we are always asynchronous
      handle({
        onFulfilled: onFulfilled,
        onRejected: onRejected
      });
    }, 0);
  }

  doResolve(fn, resolve, reject);
}

注意我们必须如何处理 .done 在 Promise 被实现/被拒绝之前和之后被调用的情况。

我们几乎有一个完整的 Promise 实现,但是,正如您在构建自己的实现时已经注意到的那样,我们需要一个返回 Promise 的 .then 方法。

我们可以从.done 轻松构建这个:

this.then = function (onFulfilled, onRejected) {
  var self = this;
  return new Promise(function (resolve, reject) {
    return self.done(function (result) {
      if (typeof onFulfilled === 'function') {
        try {
          return resolve(onFulfilled(result));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return resolve(result);
      }
    }, function (error) {
      if (typeof onRejected === 'function') {
        try {
          return resolve(onRejected(error));
        } catch (ex) {
          return reject(ex);
        }
      } else {
        return reject(error);
      }
    });
  });
}

请注意我们现在是如何免费获得您苦苦挣扎的东西的,因为resolve 接受一个 Promise 并等待它被解决。

注意我没有测试过这个 Promise 实现(尽管据我所知它是正确的)。您应该根据 Promises/A+ 测试套件 (https://github.com/promises-aplus/promises-tests) 测试您构建的任何实现,并且还可能会发现 Promises/A+ 规范 (https://github.com/promises-aplus/promises-spec) 有助于确定算法的任何特定部分的正确行为。作为最终资源,promise 是 Promise 规范的极简实现。

【讨论】:

  • 我已将此答案作为文章交叉发布在promisejs.org/implementing
  • 对于各州,我宁愿使用ANTICIPATINGFULFILLEDBROKEN。 :P
  • then 是 promise 实例上的一个方法。它用于提取承诺的值(通过注册处理程序)并通过返回新的承诺来转换承诺。它与Array.prototype.map 非常相似,除了promise。
  • 实际调用resolve 的时间是什么时候?传递给 promise 的函数由 doResolve 调用,但它传递了一个从未显式调用的函数(似乎调用 resolve)。请解释一下。
  • 你可能对处理程序是正确的,这不是一个经过良好测试的实现。
【解决方案4】:

我的解决方案

function Promise(resolver){
    if(typeof resolver !== 'function') {
        throw new TypeError(`Promise resolver ${resolver} is not a function`)
    }
    this.state = 'pending'
    this.value = void 0
    try{
        resolver(this.resolve.bind(this), this.reject.bind(this))
    }catch(error){
        this.reject.call(this,error)
    }
}

Promise.prototype.resolve = function(value) {
    if(this.state !== 'pending') return
    this.value = value
    this.state = 'fulfilled'    
    setTimeout( () => {
        if(!this.onFulfilled) return
        this.onFulfilled(value)
    }, 0)
};

Promise.prototype.reject = function(reason){
    if(this.state !== 'pending') return
    this.value = reason
    this.state = 'rejected'
    setTimeout( () => {
        if(this.onRejected){
            this.onRejected(reason)
        }else{
            throw `Uncaught (in promise) ${reason}`
        }
    }, 0)
};

Promise.prototype.then = function(fulfilled, rejected){
    if ( typeof fulfilled !== 'function' && typeof rejected !== 'function' ) {
        return this;
    }
    if (typeof fulfilled !== 'function' && this.state === 'fulfilled' ||
        typeof rejected !== 'function' && this.state === 'rejected') {
        return this;
    }
    var self = this
    return new Promise( (resolve, reject) => {
        if(fulfilled && typeof fulfilled == "function"){
            var onFulfilled = function (){
                try{
                    var result = fulfilled(self.value)
                    if(result && typeof result.then === 'function'){
                        result.then(resolve, reject)
                    }else{
                        resolve(result)
                    }
                }catch(error){
                    reject(error)
                }
            }
            if(self.state === 'pending'){
                self.onFulfilled = onFulfilled
            }else if(self.state === 'fulfilled'){
                onFulfilled()
            }
        }
        if(rejected && typeof rejected == "function"){
            var onRejected = function (){
                try{
                    var result = rejected(self.value)
                    if(result && typeof result.then === 'function'){
                        result.then(resolve, reject)
                    }else{
                        resolve(result)
                    }
                }catch(error){
                    reject(error)
                }
            }
            if( self.state === 'pending'){
                self.onRejected = onRejected
            }else if(self.state === 'rejected'){
                onRejected()
            }
        }
    })
}

/*
 *  the methods don't in Promise/A+ 
 */
Promise.prototype.catch = function(onRejected){
    return this.then(null, onRejected)
}

Promise.all = function(iterable){
    if(typeof iterable[Symbol.iterator] !== 'function'){
        throw new TypeError(`${iterable[Symbol.iterator]} is not a function`)
    }
    // Array,TypedArray,String,arguments ==> length; Map,Set ==> size 
    let len = [...iterable].length, i = 0, counter = 0, res = [];
    return new Promise( (resolve, reject) => {
        for(let item of iterable){
            ( (i) => {
                Promise.resolve(item).then(function(value){
                    counter++
                    res[i] = value
                    if(counter == len){
                        resolve(res)
                    }
                },function(reason){
                    if(!called){
                        reject(reason)
                    }
                })
            })(i++)
        }
    })
}

Promise.race = function(iterable){
    if(typeof iterable[Symbol.iterator] !== 'function'){
        throw new TypeError(`${iterable[Symbol.iterator]} is not a function`)
    }
    return new Promise( (resolve,reject) => {
        for(let item of iterable){
            Promise.resolve(item).then(function(value){
                resolve(value)
            },function(reason){
                reject(reason)
            })
        }
    })
}

Promise.resolve = function(value){
    //if(value instanceof this) return value
    //if(value instanceof Promise) return value
    if(value.constructor !== Promise) return value
    return new Promise( (resolve,reject) => {
        if(value && typeof value === 'object' && typeof value.then === 'function'){
            resolve( value.then( v => v))
        }else{
            resolve(value)
        }
    })
}

Promise.reject = function(reason){
    return new Promise( (resolve,reject) => {
        reject(reason)
    })
}

【讨论】:

    【解决方案5】:

    这一切似乎都极其复杂。我认为有一个非常简单的递归解决方案。为简洁起见,我将省略拒绝,但它与 resolve 几乎相同,只是你停止链。

    var MyPromise = function(callback) {
      this.callbacks = [];
      callback(this.resolve.bind(this));
     }
    
    MyPromise.prototype.resolve = function(data) {
      var callback = this.callbacks.pop();
      var result =  callback(data);
    
      if (!result) return;
    
      if (result instanceof MyPromise) {
        var resolve = this.resolve.bind(this);
        return result.then(function(d) {
            return resolve(d);
        });
      }
    
      return this.resolve(result);
    
    }
    
    MyPromise.prototype.then = function(callback) {
      this.callbacks.unshift(callback);
      return this;
    }
    

    【讨论】:

    • 这将失败,一个简单的非异步代码解析该值并且不支持链接和不拒绝。基本上它不是一个承诺。
    猜你喜欢
    • 2020-09-20
    • 2017-02-23
    • 1970-01-01
    • 1970-01-01
    • 2016-10-25
    • 2014-12-29
    • 1970-01-01
    • 1970-01-01
    • 2013-02-21
    相关资源
    最近更新 更多