Promise has several apis in the latest project, which looks really cool on the code structure. Then thought to go to the Internet to find a few promise library source code analysis of the article. But after reading several articles, I still couldn’t understand them very well. Then I saw a translated article that said the author of q.js library had a progressive explanation of the promise implementation. After reading it, I found it was not bad.

Q.js author source code analysis: Q.js author Promise progressive explanation of implementation

Find the source code translation of Promise online. The article is well translated in some places, but it is not smooth to read because it lacks some content compared with the original. So I also translated it according to the original. By the way, this article is for those of you who have used Promise. If you haven’t already. You can turn right ruan Yifeng promise to explain


A minimalist asynchronous callback

If you were writing a function that did not return the value immediately, but waited a few seconds before returning the result of execution, what would you say? Think about it for a few seconds.

The simplest way, of course, is to write a callback function that relies on a timer to return a value, such as the one below

var oneOneSecondLater = function (callback) {
    setTimeout(function () {
        callback(1);
    }, 1000);
};
Copy the code

This is a simple solution to the problem, but there are improvements, such as the ability to add a hint when code execution errors occur.

Var maybeOneOneSecondLater = function (callback, errback) {setTimeout(function () { If (math.random () <.5) {callback(1); } else { errback(new Error("Can't provide one.")); }}, 1000); };Copy the code

A common approach is to provide a tool that returns values and throws errors at the same time. The above example demonstrates providing both callback and error handling. But it’s too customized, and it’s not good.

Ii. Basic prototype design of Promise

So for the most part, instead of simply returning a value and throwing an exception, we expect that the function will usually return an object that represents the result of a successful or failed execution, and that return object is a promise. A promise is a promise, and it will be resolved.

Next we started iterating on promises. Let’s design a promise model with a “then” method that allows us to register callback functions and delay execution.

var maybeOneOneSecondLater = function () { var callback; setTimeout(function () { callback(1); }, 1000); return { then: function (_callback) { callback = _callback; }}; }; maybeOneOneSecondLater().then(callback1);Copy the code

The code is done. But a closer look reveals that the scheme still has two drawbacks

  • One is that the scheme can now execute only one added callback function. The best practice is that every callback added through THEN is notified and executed sequentially.
  • The second is that if the callback function is added by then after the promise is created 1s, it will not be called.

Type on the blackboard, pay attention, and then start making promises.

Normally, we want to be able to receive any number of callbacks and still register callbacks regardless of whether they time out. To do this, we’ll create a promise object with two features.

We’ve designed a defer object for the time being. Its return value is a two-part object (promise), one to register observers (the “then method adds callbacks), and one to tell all observers to execute code (resolve to execute all callbacks added earlier).

When a promise is not resolved, all callbacks are stored in a “pengding” array.

When a promise is resolved, all previously stored callbacks are immediately executed, and when all callbacks have been executed, states are distinguished by “pengding”.

let defer = () => { let pending = [],value; return { resolve(_value){ value = _value for(let i = 0; i < pending.length; i++){ pending[i](value) } pending = undefined; }, then(_callback){ if(pending){ pending.push(_callback) }else{ _callback(); } } } } let oneOneSecondLater = () => { let result = defer(); setTimeout(()=> { result.resolve(1); }, 1000); return result; }; oneOneSecondLater().then(callback);Copy the code

This first step is critical, because we can do it now

  1. You can add as many callbacks as you want at any time;
  2. You can manually decide when to resolve;
  3. When a promise is resolved, a callback can be added, but it is executed immediately

But there are some problems, like

  1. Defer can be executed multiple times by resolve, and we didn’t give an error message. And in fact, to avoid malicious or unintentional attempts to repeatedly resolve, we only allow the first call to notify the callback and execute.
  2. Add callbacks can only be added by defer.then, not by chain, i.e. Defer.then (callback).then(callback)

So let’s fix the first problem

let defer = () => { let pending = [],value return { resolve(_value){ if(pending){ value = _value for(let i = 0; i < pending.length; i++){ pending[i](value) } pending = undefined; }else{ throw new Error("A promise can only be resolved once.") } }, then(_callback){ if(pending){ pending.push(_callback) }else{ _callback(); }}}}Copy the code

Ok, now that we’ve guaranteed that we can’t repeat defer.resolve(), we’d also like to be able to add callbacks via chained calls. However, the only way to add callbacks is to defer().then(callback1),defer().then(callback2),defer().then(callback3), which is obviously not what we want. We’ll do it step by step.

Separation of responsibilities

But before we implement the chain callback, we want to separate the responsibilities of our promise, one registered observer, one implemented observer, for late structure. According to the principle of least authorization, we want to allow someone to add observers only if they grant a promise; If someone is delegated to a Resolver, he should only be able to decide when to deliver the solution. Because numerous experiments have shown that any inevitable overreach makes subsequent changes difficult to maintain. (In fact, we want to migrate the then functionality with callback to promise, from deferred. Then to deferred. Promise. then to keep the functionality pure)

let defer = () => { let pending = [],value; return { resolve(_value){ if(pending){ value = _value for(let i = 0; i < pending.length; i++){ pending[i](value) } pending = undefined; }else{ throw new Error("A promise can only be resolved once.") } }, promise: { then (callback) { if (pending) { pending.push(callback); } else { callback(value); } } } } }Copy the code

Once the separation of responsibilities is complete, we can move on to a key transformation

4. Chain call of promise

To implement chained callback, we first need to be able to accept the value of the previous callback in the next callback function. Building on the separation of responsibilities from the previous step, we’re going to take the very big step of using old promises to drive new promises. We want to use the promise combination to deliver values.

For example, let you write an additive function that accepts the sum of numbers returned by two callback functions. You can think about how to do that.

var twoOneSecondLater = function (callback) {
    var a, b;
    var consider = function () {
        if (a === undefined || b === undefined)return;
        callback(a + b);
    };
    oneOneSecondLater(function (_a) {
        a = _a;
        consider();
    });
    oneOneSecondLater(function (_b) {
        b = _b;
        consider();
    });
};

twoOneSecondLater(function (c) {
    // c === 2
});

Copy the code

The above method does this, but it is vulnerable because we need extra code to determine if the added numbers are valid when we perform the addition function.

Therefore, we want to implement the above requirements with less code, such as the following

Var a = oneOneSecondLater(); var b = oneOneSecondLater(); var c = a.then(function (a) { return b.then(function (b) { return a + b; }); });Copy the code

Callback1 is passed to Callback2 and callback2 is passed to Callback3. To achieve the effect of the above example, we need to implement the following

  • Each THEN method must return a promise
  • Each promise that is resolved must return a new promise or an implemented value
  • Resolve (_value); resolve(_value); resolve(_value);

We implement a function that passes the value to the next callback

let ref = (value) => { return { then(callback){ callback(value); }}}Copy the code

However, considering that sometimes the returned value is not just a value, but also a promise function, we need to add a judgment

let ref = (value) => { if(value && typeof value.then === "function"){ return value; } return { then(callback){ callback(value); }}}Copy the code

This way we don’t have to worry about whether the value passed in is a normal value or a promise.

Next, to enable the then method to return a promise, let’s modify the THEN method; We force the return value of the callback to be passed to the next Promise and returned immediately. This example stores the value of the callback and executes it in the next callback. But the third point above is not implemented because the return value might be a promise, so let’s go ahead and improve the method

let ref = (value) => { if(value && typeof value.then === "function"){ return value; } return { then(callback){ return ref(callback(value)); }}}Copy the code

With this enhancement, you’re basically ready to get the last callback value and continue the chain call.

We then consider the more complex case where the callback stored in defer will be invoked at some point in the future. We then need to encapsulate the callback in defer, where we will use the then method to drive the next promise and pass a return value.

In addition, the resolve method should handle cases where it is itself a PROMISE, and resolve can pass a value to a Promise. Because either ref or defer can return a then method. If the promise is of type REF, the callback will be executed immediately via THEN (callback). If the promise is of type defer, the callback is temporarily stored and depends on the next THEN (callback) call; So the callback can listen for a new promise so that it can get a fully executed value.

According to the above requirements, the following final version of promise is obtained

let isPromise = (value) => { return value && typeof value.then === "function"; }; let ref = (value) => { if (value && typeof value.then === "function") return value; return { then (callback) { return ref(callback(value)); }}; }; let defer = () => { let pending = [], value; return { resolve: function (_value) { if (pending) { value = ref(_value); // values wrapped in a promise for (let i = 0, ii = pending.length; i < ii; i++) { let callback = pending[i]; value.then(callback); // then called instead } pending = undefined; } }, promise: { then: function (_callback) { let result = defer(); // callback is wrapped so that its return // value is captured and used to resolve the promise // that "then" returns let callback = function (value) { result.resolve(_callback(value)); }; if (pending) { pending.push(callback); } else { value.then(callback); } return result.promise; }}}; }; let a = defer(); a.promise.then(function(value){console.log(value); return 2}).then(function(value){console.log(value)}); a.resolve(1);Copy the code

Defer was divided into two parts: promise and resolve

At this point, the basic promise functionality has been implemented, can be invoked chained, and can be resolved at some point in the future under its own control. Next is the enhancement of function and complement.

This part of the callback is basically written, read the description of the original text for a long time, look at the code to understand what the author is trying to say. But my English is not very good, and my writing is halting. ╮(╯▽╰)╭, I still feel some places to write wrong. Hopefully someone can correct the error.

Provide the wrong callback

To implement error message passing, we also need an error callback function (errback). Just like the callback is called when the promise is fully executed, it tells us to execute the errback and the reason for the rejection.

Implement a function similar to ref above.

let reject = (reason) => { return { then(callback,errback){ return ref(errback(reason); }}}Copy the code

The simplest way to do this is to execute the code as soon as you listen for a return value

reject("Meh.").then((value) => {},(reason) => {
    throw new Error(reason);
}
Copy the code

So let’s improve the promsie API and introduce “errback”.

To add error callbacks to the code, defer needs to add a new container to add success and error callbacks. Therefore, there is only one type of callback stored in pending. We need to re-design an array ([callback,errback]) containing both successful and error callbacks, depending on the arguments passed in to then.

var defer = function () { var pending = [], value; return { resolve: function (_value) { if (pending) { value = ref(_value); for (var i = 0, ii = pending.length; i < ii; i++) { // apply the pending arguments to "then" value.then.apply(value, pending[i]); } pending = undefined; } }, promise: { then: function (_callback, _errback) { var result = defer(); var callback = function (value) { result.resolve(_callback(value)); }; var errback = function (reason) { result.resolve(_errback(reason)); }; if (pending) { pending.push([callback, errback]); } else { value.then(callback, errback); } return result.promise; }}}; }; let ref = (value) => { if (value && typeof value.then === "function") return value; return { then: function (callback) { return ref(callback(value)); }}; }; let reject = (reason) => { return { then: function (callback, errback) { return ref(errback(reason)); }}; };Copy the code

The code is done, but there are still areas for improvement.

All then functions must provide an error callback (_errback), or they will fail if they do not. So the simplest solution is to provide a default callback function. It even goes so far as to say that it is ok to ignore the success callback (_callback) if it is needed only for error callbacks. So to satisfy this requirement, we provide a default callback function for both _callback and _errback. (Well, I just think this is a good library for fault tolerance.)

var defer = function () { ... return{ ... promise : { then: function (_callback, _errback) { var result = defer(); / / provide a default success callbacks and error correction, _callback, =, _callback, | | function (value) {/ / the default return value; }; _errback = _errback | | function (reason) {/ / the default refused to return reject (reason); }; var callback = function (value) { result.resolve(_callback(value)); }; var errback = function (reason) { result.resolve(_errback(reason)); }; if (pending) { pending.push([callback, errback]); } else { value.then(callback, errback); } return result.promise; } } } } }Copy the code

Ok, we have now implemented the completed version of the receive construct or implicit error callback step

Six, security and stability

One area we need to improve is to ensure that callbacks and errbacks are called in the future in the same order as they were registered. This significantly reduces the possibility of flow control errors in asynchronous programming. An interesting little example is given.

var blah = function () {
    var result = foob().then(function () {
        return barf();
    });
    var barf = function () {
        return 10;
    };
    return result;
};
Copy the code

The above function will either throw an exception or execute successfully and return a value of 10. And what determines which result is whether or not foob() is in the right order. Because we want the callback to succeed even if it is delayed in the future.

We add an enQueue method that uses setTimeout asynchrony to add all callbacks to the task queue in order, ensuring that the code executes in order.

let enqueue = (callback) => { setTimeout(callback,1) } let enqueue = (callback) => { //process.nextTick(callback); // NodeJS setTimeout(callback, 1); // Naive browser solution}; let defer = function () { let pending = [], value; return { resolve: function (_value) { if (pending) { value = ref(_value); for (let i = 0, ii = pending.length; i < ii; i++) { enqueue(function () { value.then.apply(value, pending[i]); }); } pending = undefined; } }, promise: { then: function (_callback, _errback) { let result = defer(); _callback = _callback || function (value) { return value; }; _errback = _errback || function (reason) { return reject(reason); }; let callback = function (value) { result.resolve(_callback(value)); }; let errback = function (reason) { result.resolve(_errback(reason)); }; if (pending) { pending.push([callback, errback]); } else { // XXX enqueue(function () { value.then(callback, errback); }); } return result.promise; }}}; }; let ref = function (value) { if (value && value.then) return value; return { then: function (callback) { let result = defer(); // XXX enqueue(function () { result.resolve(callback(value)); }); return result.promise; }}; }; let reject = function (reason) { return { then: function (callback, errback) { var result = defer(); // XXX enqueue(function () { result.resolve(errback(reason)); }); return result.promise; }}; };Copy the code

The required callbacks are added to the queue in order

The author has considered some new problems, such as

  • Callback or errback must be called in the same order
  • Callback or errback may be called simultaneously
  • Callback or errback may be called multiple times

So we need to find a callback function for then, in order to make sure that if the program fails in the callback function, we can go to the error function. (in fact, it is a library fault-tolerant processing, to ensure that the code error does not interrupt the execution of the program).

Encapsulating promises with the when method prevents errors, ensures that there are no unexpected errors, including unnecessary event flow control, and also keeps callback and errback separate.

var when = function (value, _callback, _errback) { var result = defer(); var done; _callback = _callback || function (value) { return value; }; _errback = _errback || function (reason) { return reject(reason); }; var callback = function (value) { try { return _callback(value); } catch (reason) { return reject(reason); }}; var errback = function (reason) { try { return _errback(reason); } catch (reason) { return reject(reason); }}; enqueue(function () { ref(value).then(function (value) { if (done) return; done = true; result.resolve(ref(value).then(callback, errback)); }, function (reason) { if (done) return; done = true; result.resolve(errback(reason)); }); }); return result.promise; };Copy the code

The full version of this step

6. Message delivery

At this point, promise has become a class that accepts messages. The Deferred Promise executes the corresponding callback function based on the received message, returning the corresponding value. When you receive the value of complete success, the successful callback in then returns MSG; Getting the wrong value executes the error callback in then, returning the cause of the error

So we can basically assume that the promise class will accept any value, including “then/when”. This is useful for listening on functions that do not execute immediately. For example, when you send a network request, wait for the return value before you can execute the function. We wasted a lot of time waiting for the request to come back and forth, as if promise had opened a separate thread on the computer to listen for the return values and then execute the corresponding callback function.

It’s a bit of a breakdown here, hold your head, and then I feel a bit of a headache. I’ll make it up later, because the basic promise has come out. Then there’s the promise of another requirement

Next, we’ll package a new kind of promise based on methods that can send arbitrary messages, such that “get”, “put”, “POST” can send corresponding messages, and can execute corresponding promises based on the results returned.


The first time I tried to translate, it was really a physical work, it took nearly 2 days, the whole person was fried. But fortunately, I understand a little better than before congratulations. The original article roughly explains the basic promise structure, but there are still many methods that have not been analyzed. Next, I will implement the following promise.all method according to my own ideas. If the writing is not good, welcome to correct, help me improve, thank you. (Manual face covering)