30 minutes to make you understand the promise principle

Source: Internet
Author: User

This article comes from NetEase cloud community.

A while ago recorded some of the general usage of promise, this article went further to a level, to analyze and analyze how this rule mechanism of promise is implemented. PS: This article is suitable for people who have some knowledge of the usage of promise, and if you are not familiar with its usage, you can step into my previous blog post .

The Promise source code in this article is written according to the promise/a+ specification (do not want to see the English version of the promise/a+ standard Chinese translation )

Introduction

In order to make it easier for everyone to understand, we start from a scene to explain, so that we can follow the thought of thinking, I believe you will be easier to read.

Consider the following request handling for obtaining a user ID

//例1function getUserId() {    return new Promise(function(resolve) {        //异步请求        http.get(url, function(results) {            resolve(results.id)        })    })}getUserId().then(function(id) {    //一些处理})

getUserIdThe method returns a promise callback that can be then registered by its method (note 注册 the word) promise when the asynchronous operation succeeds. This way of execution makes asynchronous calls very handy.


Principle Analysis

So how does this function happen Promise ? In fact, according to the above sentence, the realization of a most basic embryonic form is very easy.

Minimalist Promise Prototype

function Promise(fn) {    var value = null,        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调    this.then = function (onFulfilled) {        callbacks.push(onFulfilled);    };    function resolve(value) {        callbacks.forEach(function (callback) {            callback(value);        });    }    fn(resolve);}

The above code is simple, and the general logic is this:

    1. Call the then method, will want to Promise execute when the asynchronous operation succeeds the callback to put in the callbacks queue, actually is registers the callback function, may think to the observer pattern direction;
    2. When an instance is created, the Promise function passed in will be given a parameter of the function type, that is resolve , it receives a parameter value, which represents the result returned by the asynchronous operation, and when the one-step operation succeeds, the user invokes the resolve method, and the actual action is to callbacks actually perform the Callback one by one in the queue is executed;

Can be combined with 例1 the code to see, first new Promise , promise the passed function sends an asynchronous request, then invokes the promise object's then properties, registers the request for a successful callback function, and then when the asynchronous request is sent successfully, resolve(results.id) the method executes thenthe callback array that the method registers.

Believe that careful people should be able to see that the then method should be capable of chaining calls, but the above basic simple version obviously cannot support chained calls. It is then also very simple to make the method support chained calls:

this.then = function (onFulfilled) {    callbacks.push(onFulfilled);    return this;};

See? A simple sentence can be implemented like the following chained calls:

// 例2getUserId().then(function (id) {    // 一些处理}).then(function (id) {    // 一些处理});


Add delay mechanism

The careful classmate should find that the above code may still have a problem: if the then function is executed before the method registers the callback, resolve what should I do? For example, promise internal functions are synchronous functions:

// 例3function getUserId() {    return new Promise(function (resolve) {        resolve(9876);    });}getUserId().then(function (id) {    // 一些处理});

This is clearly not allowed, and the Promises/A+ specification explicitly requires callbacks to be executed asynchronously to ensure a consistent and reliable order of execution. So we have to add some processing to ensure that the resolve then method has registered all callbacks before execution. We can do this by transforming the following resolve function:

function resolve(value) {    setTimeout(function() {        callbacks.forEach(function (callback) {            callback(value);        });    }, 0)}

The idea of the above code is also very simple, is through the setTimeout mechanism, the execution of the resolve callback logic to the end of the JS task queue to ensure that at the resolve time of execution, the then callback function of the method has been registered to complete.

However, there seems to be a problem here: if the Promise asynchronous operation has succeeded, then the callback registered before the asynchronous operation succeeds, but the callback that is Promise called after the asynchronous operation succeeds then will never be executed again, which is obviously not what we want.


Join status

Well, in order to solve the problem thrown in the previous section, we have to join the state mechanism, which is known to all, pending fulfilled rejected .

Promises/A+The 2.1 in the specification Promise States explicitly stipulates that it pending can be converted to fulfilled or rejected only once, that is, if it is converted to a pending fulfilled state, then it cannot be converted to rejected . and fulfilled the rejected state can only be transformed from one pending another and cannot be converted between the two. A picture wins thousands of words:

The improved code is this:

function Promise(fn) {    var state = 'pending',        value = null,        callbacks = [];    this.then = function (onFulfilled) {        if (state === 'pending') {            callbacks.push(onFulfilled);            return this;        }        onFulfilled(value);        return this;    };    function resolve(newValue) {        value = newValue;        state = 'fulfilled';        setTimeout(function () {            callbacks.forEach(function (callback) {                callback(value);            });        }, 0);    }    fn(resolve);}

The idea of the above code is this: When executed, the state is set to the new callback that was added after that, and is resolve fulfilled then executed immediately.

There's no place to state set it up rejected , and in order to focus on the core code, there's a section behind this question that will be added.


Chain-type Promise

So here's the problem again, if the user again then the registration of the function is still one Promise , how to solve? For example, the following 例4 :

// 例4getUserId()    .then(getUserJobById)    .then(function (job) {        // 对job的处理    });function getUserJobById(id) {    return new Promise(function (resolve) {        http.get(baseUrl + id, function(job) {            resolve(job);        });    });}

This scenario is believed to promise be used by people who know there will be many, so the so-called chain-like Promise .

A chain Promise is the promise fulfilled beginning of the next promise (rear-neighbor) after the current state is reached promise . So how do we converge on promise the current and the neighboring? promise (This is the difficulty here).

In fact, it is not spicy, as long as the then method inside return a promise good. Promises/A+the 2.2.7 in the norm is so-called (smiling face) ~

Below to see the hidden mystery of the then method and resolve method to retrofit code:

function Promise (FN) {var state = ' pending ', value = null, callbacks = []; This.then = function (onfulfilled) {return new Promise (function (resolve) {handle ({OnF ulfilled:onfulfilled | |        NULL, resolve:resolve});    });    };            function handle (callback) {if (state = = = ' pending ') {Callbacks.push (callback);        Return            }//If then nothing passes anything if (!callback.onfulfilled) {callback.resolve (value);        Return        } var ret = callback.onfulfilled (value);    Callback.resolve (ret); } function Resolve (NewValue) {if (NewValue && (typeof newvalue = = = ' object ' | | typeof newvalue = = = ' Fun            Ction ') {var then = Newvalue.then;                if (typeof then = = = ' function ') {Then.call (newvalue, resolve);            Return        }} state = ' fulfilled '; Value = newvalUe            SetTimeout (function () {Callbacks.foreach (function (callback) {handle (callback);        });    }, 0); } fn (resolve);}

We combine 例4 the code, analyze the above code logic, in order to facilitate reading, I put 例4 the code here:

// 例4getUserId()    .then(getUserJobById)    .then(function (job) {        // 对job的处理    });function getUserJobById(id) {    return new Promise(function (resolve) {        http.get(baseUrl + id, function(job) {            resolve(job);        });    });}

  1. thenmethod, a new instance is created and returned Promise , which is the basis of the serial and Promise supports chaining calls.
  2. handleThe method is an promise internal method. thenmethod is passed to the onFulfilled Promise resolve push current promise callbacks queue, which is the link between the current promise and the rear neighbor promise , as well as when a new instance is created. The key to the problem (here must be a good analysis of the role of handle).
  3. getUserIdThe resulting promise (short getUserId promise ) asynchronous operation succeeds, executes its internal method resolve , and the passed parameter is the result of the asynchronous operationid
  4. Call the handle method callbacks to process the callback in the queue: getUserJobById method, generate a new promise ( getUserJobById promise )
  5. Executes the getUserId promise then new promise (called) method that was generated by the method before the passed-in bridge promise resolve parameter is getUserJobById promise . In this case, the method is resolve passed in to the method getUserJobById promise and is then returned directly.
  6. getUserJobById promisewhen the asynchronous operation succeeds, execute the callbacks callback in it: getUserId bridge promise the resolve method in
  7. The callback in the last execution getUserId bridge promise of the next neighbor promise callbacks .

More straightforward can look at the following figure, a picture of thousands of words (are based on their own understanding of the painting, if not welcome correct):

Failure handling

When an asynchronous operation fails, mark its status as rejected , and execute the registered failure callback:

//例5function getUserId() {    return new Promise(function(resolve) {        //异步请求        http.get(url, function(error, results) {            if (error) {                reject(error);            }            resolve(results.id)        })    })}getUserId().then(function(id) {    //一些处理}, function(error) {    console.log(error)})

With the fulfilled experience of previous processing states, it is easy to support error handling by adding new logic to the registration callback and processing state changes:

function Promise (FN) {var state = ' pending ', value = null, callbacks = []; This.then = function (onfulfilled, onrejected) {return new Promise (function (resolve, reject) {handle ( {onfulfilled:onfulfilled | | null, onrejected:onrejected | | null, resolve:        Resolve, reject:reject});    });    };            function handle (callback) {if (state = = = ' pending ') {Callbacks.push (callback);        Return } var cb = state = = = ' fulfilled '?        callback.onFulfilled:callback.onRejected, ret;            if (cb = = = NULL) {cb = state = = = ' fulfilled '? Callback.resolve:callback.reject;            CB (value);        Return        } RET = CB (value);    Callback.resolve (ret); } function Resolve (NewValue) {if (NewValue && (typeof newvalue = = = ' object ' | | typeof newvalue = = = ' Fun Ction ')) {var thEn = Newvalue.then;                if (typeof then = = = ' function ') {Then.call (newvalue, resolve, reject);            Return        }} state = ' fulfilled ';        value = newvalue;    Execute ();        } function reject (reason) {state = ' rejected ';        value = reason;    Execute ();                } function Execute () {setTimeout (function () {Callbacks.foreach (callback) {            Handle (callback);        });    }, 0); } fn (resolve, reject);}

The code above adds a new reject method that is called when an asynchronous operation fails, while extracting resolve and reject sharing the part to form a execute method.

Error bubbling is a feature that has been supported by the above code and is very useful. handlewhen a callback that does not specify an asynchronous operation fails is found, it is bridge promise set directly (the back then of the function returned promise ) to the rejected state, so that the effect of subsequent failed callbacks is achieved. This facilitates the Promise cost of serial failure processing because a set of asynchronous operations often corresponds to an actual function, and the failure handling method is usually consistent:

//例6getUserId()    .then(getUserJobById)    .then(function (job) {        // 处理job    }, function (error) {        // getUserId或者getUerJobById时出现的错误        console.log(error);    });

Exception handling

A careful classmate will think: What if the code goes wrong when the successful callback and the failed callback are executed? For such exceptions, you can use the try-catch catch error and bridge promise set the rejected state. The handle method is modified as follows:

function handle(callback) {    if (state === 'pending') {        callbacks.push(callback);        return;    }    var cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected,        ret;    if (cb === null) {        cb = state === 'fulfilled' ? callback.resolve : callback.reject;        cb(value);        return;    }    try {        ret = cb(value);        callback.resolve(ret);    } catch (e) {        callback.reject(e);    } }

If you are executing multiple times in an asynchronous operation, resolve or if reject subsequent callbacks are processed repeatedly, you can resolve them by using a flag bit built in.

Summarize

At the beginning of the promise source code is not a good understanding of the then and resolve function of the operating mechanism, but if you calm down, in turn, according to the implementation of promise logic to deduce, it is not difficult to understand. Here must pay attention to the point is: Promise inside the then function is only to register the subsequent need to execute the code, the real execution is executed in the Resolve method, clear this layer, and then to analyze the source code will be more labor-saving.

Now review the implementation of promise, which mainly uses the observer pattern in the design pattern:

    1. The Observer method is registered with the Observer Promise object through the Promise.prototype.then and Promise.prototype.catch methods, and a new promise object is returned so that it can be called in a chain.
    2. The Observer manages the state transitions of the internal pending, fulfilled, and rejected, while actively triggering state transitions and notifying observers through the resolve and reject methods passed in the constructor.

Reference documents

Deep understanding of Promise

JavaScript Promises ... In Wicked Detail

This article from the NetEase cloud community, by the author Gu Jing authorized release.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.