基本的 Javascript promise 实现尝试

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23772801/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 01:32:01  来源:igfitidea点击:

Basic Javascript promise implementation attempt

javascriptpromise

提问by spirytus

To gain better understanding of how promises work in Javascript I decided to give it a try and code basic implementation myself.

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

Basically I want to implement Promises Object (I call it Aaa in my code) that takes function as an argument. This function can call resolve to resolvethe promise, or reject to rejectit. The basic implementation and usage is below. Not sure if the second argument is accepteable according to promise specs, but that's what I got so far.

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

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");

});

So now the promise can be created, called and resolved. Each thenmethod will return new Aaa (Promise) so these can be chained. Now the code below uses promise created above and chains thencallbacks. Each thenreturns new promise and in this case it seems to work fine:

所以现在可以创建、调用和解析 promise。每个then方法都将返回新的 Aaa(承诺),因此可以链接它们。现在下面的代码使用上面创建的承诺和链式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);
});

the output I'm getting is:

我得到的输出是:

ggg
aaa 
bbb 
ggg 
ggg 
fff 
ccc 
good 
ddd 
undefined 

The problem is however when one of the thencalls returns a promise:

然而,问题是当其中一个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);
});

The output is:

输出是:

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

The call then where dddis output should not be called UNTIL the returned promise we just added is resolved.

调用 then where dddis output 不应该被调用,直到我们刚刚添加的返回的 promise 被解析。

How would that be best implemented?

如何最好地实施?

回答by ForbesLindesay

There are a number of cases you're not handling here. The best bet is to start by building the promise as a state machine:

有很多情况你没有在这里处理。最好的办法是首先将 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 = [];
}

Now lets define a simple helper to use through the rest of our implementation:

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

// 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;
}

Next, we need to consider each of the transformations that can occur:

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

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;
  }
}

Note how resolvecan receive a Promise as its argument, but a Promise can never be fulfilled with another Promise. So we have to handle this special case.

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

Note also that a Promise can only ever be fulfilled/rejected once. We also have the problem that a third party Promise may misbehave, and we should guard our code against that. For this reason, I haven't just called result.then(resolve, reject)from within resolve. Instead, I split that into a separate function:

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

/**
 * 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)
  }
}

So now we have a completed state machine, but no way to observe or trigger the changes in state. Lets start by adding a way to trigger the state changes by passing in a resolver function.

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

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);
}

As you can see, we re-use doResolvebecause we have another un-trusted resolver. The fnmight call resolveor rejectmultiple times, and it might throw an error. We need to handle all of these cases (and that's what doResolvedoes).

如您所见,我们重用是doResolve因为我们有另一个不受信任的解析器。在fn可能要求resolvereject多次,并且它可能会引发错误。我们需要处理所有这些情况(就是doResolve这样做的)。

We now have the completed state machine, but we haven't exposed any information about what state it is in. Lets try adding a .done(onFulfilled, onRejected)method that is just like .thenexcept that it does not return a Promise and does not handle errors thrown by onFulfilledand onRejected.

我们现在已经完成了状态机,但是我们还没有暴露任何关于它处于什么状态的信息。让我们尝试添加一个.done(onFulfilled, onRejected)方法,.then除了它不返回 Promise 并且不处理onFulfilled和抛出的错误之外onRejected

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);
}

Note how we must handle the case of .donebeing called both before and after the Promise becomes fulfilled/rejected.

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

We almost have a complete promise implementation, but, as you already noticed when building your own implementation, we need a .thenmethod that returns a Promise.

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

We can build this easilly out of .done:

我们可以通过以下方式轻松构建.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);
      }
    });
  });
}

Note here how we get the thing you were struggling with for free now, because resolveaccepts a Promise and waits for it to be resolved.

请注意我们现在如何免费获得您正在努力resolve解决的问题,因为接受 Promise 并等待它被解决。

N.B.I haven't tested this Promise implementation (although it is correct to the best of my knowledge). You should test any implementation you build against the Promises/A+ test suite (https://github.com/promises-aplus/promises-tests) and may also find the Promises/A+ spec (https://github.com/promises-aplus/promises-spec) useful in figuring out what the correct behavior is for any specific part of the algorithm. As a final resource, promiseis a very minimal implementation of the Promise spec.

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

回答by trincot

(For a full Promise implementation, scroll down).

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

Some issues in your code

您的代码中的一些问题

There are several issues, but I think the main mistake in your code is that you take the argument given to the thenmethod and pass it as argument to the promise constructor:

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

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

Although both arguments are call back functions, they have a different signature, and serve entirely different purposes:

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

  • The argument to the promise constructor is a call back function which is to be executed immediately, synchronously. A functionis passed to it as first argument, with which you can resolve the promise you are creating.
  • The (first) argument to the the thenmethod, is a call back function which will only get executed later, asynchronously, when the base promise is resolved, and to which the resolved valueis passed as argument.
  • 应许构造函数的参数是一个回调函数将被执行,立即同步。一个函数作为第一个参数传递给它,您可以使用它来解析您正在创建的承诺。
  • 该方法的(第一个)参数then是一个回调函数,它只会在稍后异步执行,当基本承诺被解析时,解析的值作为参数传递给它。

You can see the difference also in your code, where you store the argument to the constructor as the fproperty. You have both this:

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

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

...where gis the resolved value, but also this:

...其中g是解析值,但也是这个:

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

...where the arguments are functions. When you create the nextPromiseyou will in fact first call fwith these two arguments, and then later, with the gargument.

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

A Promises/A+ compliant implementation from the ground up

从头开始的 Promises/A+ 兼容实现

We could build our own Promise implementation by following the requirements in the Promises/A+ specification:

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

2.1 Promise states

2.1 承诺状态

There are only 2 state transitions allowed: from pending to fulfilled, and from pending to rejected. No other transition should be possible, and once a transition has been performed, the promise value (or rejection reason) should not change.

只允许 2 种状态转换:从待处理到已完成,以及从待处理到拒绝。没有其他转换是可能的,并且一旦执行了转换,承诺值(或拒绝原因)不应改变。

Here is a simple implementation that will adhere to the above restrictions. The comments reference the numbered requirements in the above specification:

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

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
}

Of course, this does not provide the thenmethod, which is key to Promises:

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

2.2 The thenMethod

2.2then方法

This is the core of the specification. The above code can be extended to expose the thenmethod, which returns a promise and provides asynchronous execution of the appropriate thencallback, only once, providing for multiple thencalls, turning exceptions to rejections, ...etc.

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

So the following code adds the thenmethod, but also a broadcastfunction which is defined separately, because it must be called on any state change: this does not only include the effect of the thenmethod (a promise is added to a list), but also of the resolveand rejectmethods (state and value change).

所以下面的代码添加了then方法,还有一个broadcast单独定义的函数,因为它必须在任何状态改变时调用:这不仅包括then方法的效果(一个promise被添加到列表中),还包括的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);
            };
        })
    });
};

This covers almost everything, except that at the TODO:comment, the so-called Promise Resolution Procedure must be called:

这几乎涵盖了一切,除了在TODO:评论中,必须调用所谓的Promise Resolution Procedure:

2.3 The Promise Resolution Procedure

2.3 Promise 解决程序

This is a procedure that treats values that are thenables (or even promises) differently: instead of returning the value as-is, the procedure will execute the thenmethod on that value and asynchronously fulfills the promise with the value received from that thencallback. It is not mentioned in the specs, but this is interesting to perform not only in the thenmethod, but also when the main promise is resolved with such a value.

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

So the existing resolvemethod should be replaced with this "Promise Resolution Procedure", which will call the original one. The original one could be called "fulfill", to indicate it will resolve the promise always as fulfilled:

所以现有的resolve方法应该替换为这个“Promise Resolution Procedure”,它会调用原来的方法。最初的可以称为“fulfill”,表示它将始终将承诺解决为已完成:

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);
    }
}

This is now Promises/A+ compliant, at least it passes the test-suite. Yet, the Promise object exposes far too many methods and properties:

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

A Promise Object with thenonly

一个then只有一个 Promise 对象

The above built constructor creates something that is more like a Deferredobject, i.e. which exposes resolveand rejectmethods. Even worse, the statusand valueproperties are writable. So, it would be more logical to regard this as a constructor for an unsecured Deferred object, and create a separate Promise constructor which builds on that, but only exposes what is needed: a thenmethod and a constructor callback that can access resolveand reject.

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

The deferred object can then do without the constructor callback argument, and provide access to the pure promise object via a promiseproperty:

deferred 对象然后可以不用构造函数回调参数,并通过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;
};

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;
}

There are several optimisations possible to this code, such as making Deferred methods private functions, and merging similar code into shorter code blocks, but as it stands now it shows quite clearly where each requirement is covered.

这段代码有几个可能的优化,例如使延迟方法成为私有函数,并将类似的代码合并到更短的代码块中,但就目前而言,它非常清楚地显示了每个需求的覆盖范围。

Happy coding.

快乐编码。

回答by zhangfuxing

my solution

我的解决方案

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)
    })
}

回答by Nishant

I tried to implement this with ES6. Posting because it might be useful for someone else

我试图用 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;

回答by Jamie

this all seems extremely complicated. I think there's a really simple recursive solution. I'm going to omit the reject for the sake of brevity, but it's pretty much the same as resolve except you halt the chain.

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

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;
}