SoFunction
Updated on 2025-03-02

How to use Promise in asynchronous JavaScript programming


asynchronous?

I have seen the word Asynchronous in many places, but when I don’t understand this concept very well, I find that I am often regarded as “already clear” (* ̄? ̄).

If you have a similar situation, it doesn't matter. Search for this term and you can get a general explanation. Here I will give a little extra explanation of JavaScript's asynchronousness.

Take a look at this code:

var start = new Date();
setTimeout(function(){
  var end = new Date();
  ("Time elapsed: ", end - start, "ms");
}, 500);
while (new Date - start < 1000) {};

After running this code, you will get results like Time elapsed: 1013ms. The function set by setTimeout() to execute in the next 500ms is actually waited for more time than 1000ms before executing.

How to explain it? When setTimeout() is called, a delay event is queued. Then, continue executing the code after this, and more after this code until there is no code. After no code, the JavaScript thread enters idle. At this time, the JavaScript execution engine goes to look at the queue, finds the event that "should be triggered" in the queue, and then calls the processor (function) of this event. After the processor has completed execution, it returns to the queue and then looks at the next event.

Single-threaded JavaScript works in the form of an event loop through queues. Therefore, in the previous code, while drag the execution engine during the code running for up to 1000ms, and no event will be triggered until all the code is run and returned to the queue. This is the asynchronous mechanism of JavaScript.
Asynchronous puzzles in JavaScript

Asynchronous operations in JavaScript may not always be simple and easy.

Ajax is perhaps the most used asynchronous operation. Taking jQuery as an example, the code for initiating an Ajax request is generally like this:

// Ajax request schematic code$.ajax({
  url: url,
  data: dataObject,
  success: function(){},
  error: function(){}
});

Is there any problem with this writing? Simply put, not light enough. Why do we have to write callbacks such as success and error at the place where the request is initiated? If my callback has to do a lot of things, would I want to go back here to add code when I think of something?

For example, we want to complete the following thing: there are 4 URL addresses for Ajax to access, and Ajax needs to access the first one first. After the first access is completed, use the returned data obtained as a parameter and then access the second one, and then the third one after the second access is completed... so that all 4 accesses are completed. According to this writing, it seems to turn out like this:

$.ajax({
  url: url1,
  success: function(data){
    $.ajax({
      url: url2,
      data: data,
      success: function(data){
        $.ajax({
          //...
        });
      }  
    });
  }
})

You'll definitely think this code called Pyramid of Doom looks bad. If you are used to writing directly attached callbacks, you may feel that you have no idea about this asynchronous event that is passed to the next one. Naming and separating these callback functions can reduce nesting in form to make the code clear, but still not solve the problem.

Another common difficulty is to send two Ajax requests at the same time, and then do the next thing after both requests are successfully returned. Think about it, if you only attach the callbacks in the previous way at their respective call locations, does it seem a bit difficult to do?

Promise is suitable for dealing with these asynchronous operations and allowing you to write more elegant code.
Promise comes on stage

What is a Promise? Let's continue with the previous jQuery's Ajax request schematic code as an example. That code can actually be written like this:

var promise = $.ajax({
  url: url,
  data: dataObject
});
(function(){});
(function(){});

This is equivalent to the previous Ajax request code. As you can see, the addition of Promise has changed the code form. Ajax requests are "save" like variable assignments. This is encapsulation, encapsulation will make asynchronous events easier in the true sense.
Encapsulation is useful

A Promise object is like a packaged reference to an asynchronous event. Want to do something after this asynchronous event is completed? Just attach a callback to it, no matter how many of them are attached, it's fine!

jQuery's Ajax method will return a Promise object (this is a feature added by jQuery 1.5). If I have two functions do1() and do2() to execute after the asynchronous event is successfully completed, I just need to do this:

(do1);
// Other code here.
(do2);

This makes me much more free. I just need to save this Promise object and attach any number of callbacks to it at any time when I write the code, regardless of where the asynchronous event was initiated. This is the advantage of Promise.
Formal introduction

Promise is so useful to deal with asynchronous operations that it has been developed for a specification of CommonJS called Promises/A. Promise represents the return value after an operation is finished, and it has 3 states:

  1. OK (fulfilled or resolved), indicating that the operation of the Promise was successful.
  2. Negation (rejected or failed), indicates that the operation of the Promise failed.
  3. Waiting (pending), not yet obtaining positive or negative results, and is in progress.

In addition, there is a nominal state used to indicate that the Promise operation has been successful or failed, that is, a set of affirmative and negative states, called settlement. Promise also has the following important features:

  • A promise can only change from a waiting state to a positive or negative state once, and once it changes to a positive or negative state, it will never change the state again.
  • If a callback for success or failure is added after a promise ends (success or failure, same as in the previous description), the callback function is executed immediately.

Think of the Ajax operation, after making a request, wait, and then successfully receive a return or an error (failed). Is this quite consistent with Promise?

There is another good example to further explain the characteristics of Promise: jQuery's $(document).ready(onReady). The onReady callback function will be executed after the DOM is ready, but interestingly, if the DOM is ready before the code is executed, then onReady will be executed immediately without any delay (that is, it is synchronized).
Promise example
Generate Promise

Promises/A lists a series of JavaScript libraries that implement Promise, and jQuery is also included. Here is the code to generate a Promise using jQuery:

var deferred = $.Deferred();
(function(message){("Done: " + message)});
("morin"); // Done: morin

jQuery itself specifically defined a class called Deferred, which is actually Promise. The $.Deferred() method returns a newly generated Promise instance. On the one hand, use (), (), etc. to attach a callback to it, and on the other hand, call () or () to affirm or negate the promise, and any data can be passed to the callback.
Merge Promise

Do you still remember the problem of sending 2 Ajax requests at the same time as I mentioned in the previous article? Continue to take jQuery as an example, Promise will solve it like this:

var promise1 = $.ajax(url1),
promise2 = $.ajax(url2),
promiseCombined = $.when(promise1, promise2);
(onDone);

The $.when() method can merge multiple promises to obtain a new promise, which is equivalent to establishing an AND (logical and) relationship between the original multiple promises. If all the constituent promises have been successful, the merged promises will also be successful. If any constituent promises fail, the merged promises will be immediately failed.
Cascading Promise

Let’s continue with the problem of executing a series of asynchronous tasks in sequence as I mentioned earlier. It will use the most important .then() method of Promise (in the Promises/A specification, it is also used to define Promise using "objects with the then() method". The code is as follows:

var promise = $.ajax(url1);
promise = (function(data){
  return $.ajax(url2, data);
});
promise = (function(data){
  return $.ajax(url3, data);
});
// ...

The complete form of the .then() method of Promise is .then(onDone, onFail, onProgress), which looks like a simple method that can attach all kinds of callbacks at one time (.done() and .fail() are not used). That's right, you can use it like this, which is equivalent.

But the .then() method also has its more useful functions. Just like the meaning of the word then itself, it is used to clearly indicate the relationship between the asynchronous event: "first this, then (then) and that". This is called the cascade of Promise.

To cascade the promises, it is important to note that in the callback function passed to then(), you must return the promise you want to represent the next task (such as $.ajax(url2, data) in the above code). In this way, the variable assigned before will become a new Promise. If the then() callback function returns not a Promise, then() method will return the original Promise.

Should it find it a bit difficult to understand? From the perspective of code execution, the above code with multiple then() is actually finished after being run by the JavaScript engine. But it is like a script for a written stage play. After reading it once, the JavaScript engine will arrange actors to perform according to the script in the future, and the performances are asynchronous. The then() method allows you to write a pen for an asynchronous script.
Use Promise in a callback function-based API

The $.ajax() method repeatedly used in the previous article will return a Promise object, which is actually just a benefit specially provided by jQuery. The reality is that most JavaScript APIs, including native functions in them, are based on callback functions, not on Promise. In this case, you will need to do some processing by yourself.

This processing is actually relatively simple and direct. Here is an example:

var deferred = $.Deferred();
setTimeout(, 1000);
(onDone);

In this way, passing the affirmative or negative trigger of Promise as a callback to the API becomes the processing mode of Promise.
How is Promise implemented?

This article is written by Promise, and you have found that all of them are based on existing libraries that implement Promise. So, what if you want to build a promise yourself?

Q, ranked first in the library list of Promises/A, can be regarded as the most consistent with the Promises/A specification and quite intuitive implementation. If you want to know how to make a promise, you can refer to the design pattern analysis provided by Q.

Due to space limitations, this article only introduces the application of Promise. I will open a separate article in the future to detail the implementation details of Promise.

ECMAScript 6, a subsequent version of JavaScript, will provide promises natively. If you want to know how it is used, it is recommended to read JavaScript Promises: There and back again.
Conclusion

The word Promise is so tenacious that it is not suitable for translation, and you will feel that the meaning is unknown at a glance. However, it can indeed provide quite a lot of help when doing more complex asynchronous tasks in JavaScript.