Summary use of Promise in JavaScript programming

Source: Internet
Author: User

Summary use of Promise in JavaScript programming

Summary use of Promise in JavaScript programming

 


Promise core description

Although Promise already has its own specifications, the current Promise libraries differ in the Implementation Details of Promise, and some APIs are even completely different in meaning. However, the core content of Promise is the same, and it is the then method. In related terms, promise refers to an object or function with a then method that can trigger a specific action.

Promise can have different implementation methods, so the Promise core description does not discuss any specific implementation code.

First, read the core description of Promise, which means: Look, this is the result to be written. Please refer to this result and think about how to use the code to write it out.


What problems does Promise solve? Callback. For example, function doMission1 () represents the first thing. Now, what should we do if we want to do the next thing doMission2 () after this is done?

Let's take a look at our common callback modes. DoMission1 () said: "If you want to do this, give me doMission2 () and I will help you call it after the end ." So it will be:

doMission1(doMission2);


What is the Promise mode? You said to doMission1 (): "No, control is on me. You should change it. You should first return a special item to me, and then I will use it to arrange the next thing ." This special thing is Promise, which will become like this:

doMission1().then(doMission2);


It can be seen that Promise has changed the master-slave relationship of the callback mode to a position (turning over as the master !), The process relationships of multiple events can be concentrated on the main road (rather than scattered in various event functions ).

Well, how can we make such a conversion? In the simplest case, assume that the doMission1 () code is:

function doMission1(callback){  var value = 1;  callback(value);}


Then, it can be changed to the following:

function doMission1(){  return {    then: function(callback){      var value = 1;      callback(value);    }  };}


This completes the conversion. Although it is not actually a useful conversion, it has already touched on the most important implementation point of Promise, that is, Promise converts the return value to an object with the then method.

Advanced: Q's design journey

Start with defer

Design/q0.js is the first step in Q preliminary molding. It creates a tool function named defer to create Promise:

var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      value = _value;      for (var i = 0, ii = pending.length; i < ii; i++) {        var callback = pending[i];        callback(value);      }      pending = undefined;    },    then: function (callback) {      if (pending) {        pending.push(callback);      } else {        callback(value);      }    }  }};


From this source code, you can see that running defer () will get an object that contains two methods: resolve and then. Think back to jQuery's Deferred (resolve and then are also available). These two methods will be similar. Then will refer to the pending status. If it is in the waiting status, it will save the callback (push); otherwise, it will call the callback immediately. Resolve will confirm this Promise, and the updated value will run all the stored callbacks at the same time. Defer example:

var oneOneSecondLater = function () {  var result = defer();  setTimeout(function () {    result.resolve(1);  }, 1000);  return result;};


OneOneSecondLater (). then (callback );

Here, oneOneSecondLater () contains the asynchronous content (setTimeout), but let it immediately return an object generated by defer, then, place the resolve Method of the object in the end position of the asynchronous call (with the attached value or result ).

At this point, the above Code has a problem: resolve can be executed multiple times. Therefore, the State judgment should be added to resolve to ensure that resolve is valid only once. This is the design/q1.js step of Q (only difference ):

resolve: function (_value) {  if (pending) {    value = _value;    for (var i = 0, ii = pending.length; i < ii; i++) {      var callback = pending[i];      callback(value);    }    pending = undefined;  } else {    throw new Error(A promise can only be resolved once.);  }}


For the second and more calls, you can throw an error or ignore it directly.

Separate defer and promise

In the previous implementation, the object generated by defer has both the then method and the resolve method. According to the definition, promise is concerned with the then method. As for the resolve that triggers the promise to change the state, it is another thing. Therefore, Q will separate the promise with the then method from the defer with the resolve method for independent use. In this way, we seem to have clearly defined their respective responsibilities and left only some permissions, which makes the code logic clearer and easy to adjust. See design/q3.js: (q2 skipped here)

var isPromise = function (value) {  return value && typeof value.then === function;};var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      if (pending) {        value = _value;        for (var i = 0, ii = pending.length; i < ii; i++) {          var callback = pending[i];          callback(value);        }        pending = undefined;      }    },    promise: {      then: function (callback) {        if (pending) {          pending.push(callback);        } else {          callback(value);        }      }    }  };};


If you carefully compare q1, you will find that the difference is very small. On the one hand, no errors will be thrown (Instead, the second and more resolve will be directly ignored). On the other hand, the then method will be moved to an object named promise. Here, the object obtained by running defer () (called defer) points the method with the resolve method and a promise property to another object. The other object is the promise with only the then method. This completes the separation.

There is also an isPromise () function, which determines whether the object is promise by using the then method (duck-typing ). To correctly use and process the separated promise, you need to distinguish promise from other values.

Implement promise Cascade

Next is a very important step. Up to q3, the implemented promise cannot be cascaded. However, the promise you are familiar with should support the following syntax:

promise.then(step1).then(step2);


The above process can be understood as that promise will be able to create a new promise and be taken from the value of the old promise (value in the previous Code ). To achieve then cascade, you need to do the following:

The then method must return promise.

The returned promise must use the callback result passed to the then method to set its own value.

The callback passed to the then method must return a promise or value.

In design/q4.js, a tool function ref is added to achieve this:

var ref = function (value) {  if (value && typeof value.then === function)    return value;  return {    then: function (callback) {      return ref(callback(value));    }  };};


This is to process the value associated with promise. This tool function will encapsulate any value once. If it is a promise, nothing will be done. If it is not a promise, it will be packaged into a promise. Note that there is a recursion here, which ensures that the encapsulated promise can be cascade using the then method. To help you understand it, the following is an example:

ref(step1).then(function(value){  console.log(value); // step1  return 15;}).then(function(value){  console.log(value); // 15});


You can see how the value is passed, as is the case for promise cascade.

Design/q4.js converts the original defer to cascade by using this ref function:

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


The original callback (value) format is changed to value. then (callback ). After this modification, the effect is actually the same as the original one, but the value is changed to the type of promise packaging, which will be called like this.

If the then method has many changes, a new defer is generated first and the promise of the defer is returned at the end. Note that callback does not directly use the one passed to then, but adds a layer on the base and places the newly generated defer resolve method here. It can be understood that the then method will return a newly generated promise, so we need to reserve the resolve of promise. After the resolve of the old promise is run, the resolve of the new promise will also run. In this way, events can be transmitted layer by layer according to the then connection content, just like pipelines.

Add error handling

The then method of promise should include two parameters: the processing functions of the positive and negative States (onFulfilled and onRejected ). The previously implemented promise can only be changed to a positive State. Therefore, add the negative state section.

Note that the two parameters of the then method of promise are optional. Design/q6.js (q5 skipped) added the tool function reject to help implement the negative state of promise:

var reject = function (reason) {  return {    then: function (callback, errback) {      return ref(errback(reason));    }  };};


The main difference between it and ref is that the then method of the returned object will only run with the errback of the second parameter. The rest of design/q6.js is:

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++) {          value.then.apply(value, pending[i]);        }        pending = undefined;      }    },    promise: {      then: function (_callback, _errback) {        var result = defer();        // provide default callbacks and errbacks        _callback = _callback || function (value) {          // by default, forward fulfillment          return value;        };        _errback = _errback || function (reason) {          // by default, forward rejection          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;      }    }  };};


The main change here is to save the array pending only in the form of a single callback, and change it to the two types of callback that both save the positive and negative. In addition, the default affirmative and negative callback is defined in then, so that the then method meets the requirements of two optional parameters of promise.

You may notice that there is only one resolve Method in defer, and there is no reject similar to jQuery. How can I trigger the error handling? See this example:

var defer1 = defer(),promise1 = defer1.promise;promise1.then(function(value){  console.log(1: value = , value);  return reject(error happens); }).then(function(value){  console.log(2: value = , value);}).then(null, function(reason){  console.log(3: reason = , reason);});defer1.resolve(10);// Result:// 1: value = 10// 3: reason = error happens


It can be seen that every return value passed to the then method is very important, and it will determine the call result of the next then method. If an object generated by the tool function reject is returned as above, error processing is triggered.

Asynchronous Integration

Finally, design/q7.js is reached. The preceding q6 still has a problem, that is, when the then method is running, it may be synchronous or asynchronous, this depends on the function that is passed to then (for example, if a value is directly returned, that is, synchronization. If another promise is returned, it can be asynchronous ). This uncertainty may cause potential problems. Therefore, the next step of Q is to ensure that all then is converted to asynchronous.

Design/q7.js defines another tool function enqueue:

var enqueue = function (callback) {  //process.nextTick(callback); // NodeJS  setTimeout(callback, 1); // Na?ve browser solution};


Obviously, this tool function will delay any function to run in the next event queue.

Other design/q7.js modification points are (only the modified part is displayed ):

var ref = function (value) {  // ...  return {    then: function (callback) {      var result = defer();      // XXX      enqueue(function () {        result.resolve(callback(value));      });      return result.promise;    }  };};var reject = function (reason) {  return {    then: function (callback, errback) {      var result = defer();      // XXX      enqueue(function () {        result.resolve(errback(reason));      });      return result.promise;    }  };};var defer = function () {  var pending = [], value;  return {    resolve: function (_value) {      // ...          enqueue(function () {            value.then.apply(value, pending[i]);          });      // ...    },    promise: {      then: function (_callback, _errback) {          // ...          enqueue(function () {            value.then(callback, errback);          });          // ...      }    }  };};


That is, the original value. then is converted to asynchronous.

At this point, the Promise design principle q0 ~ provided by Q ~ Q7: all ends.

Conclusion

Even though this article has been so long, it is only about the basic Promise. Most Promise libraries have more APIs to meet more requirements related to Promise, such as all () and spread (). However, read here, you have learned the core idea of Promise implementation, which will be helpful for your future application of Promise.

In my opinion, Promise is an elaborate design, and it took me quite a bit of time to understand it. Q, as a typical Promise Library, is clearly defined in terms of ideas. We can feel that the complex library begins with the basic points. If we want to do similar things on our own, we should also maintain this mentality of making progress at 1.1 points.

 

 

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.