基本的 Javascript 承诺实现尝试

Posted

技术标签:

【中文标题】基本的 Javascript 承诺实现尝试【英文标题】:Basic Javascript promise implementation attempt 【发布时间】: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 的调用。

如何最好地实施?

【问题讨论】:

Related. 【参考方案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;

【讨论】:

这将失败,一个简单的非异步代码解析该值并且不支持链接和不拒绝。基本上它不是一个承诺。

以上是关于基本的 Javascript 承诺实现尝试的主要内容,如果未能解决你的问题,请参考以下文章

javascript 使用承诺实现倒计时

打字稿通用承诺返回类型

嵌套的 Javascript 承诺 - 从 Firestore 获取数据

如何调试 javascript 承诺?

通过 Javascript 中的承诺链传递状态都有哪些模式? [复制]

使用蓝鸟承诺的while循环