(turn) Promise object

http://javascript.ruanyifeng.com/advanced/promise.html#toc0
Promise is a JavaScript asynchronous operation solution. Before introducing Promises, let's give a detailed introduction to asynchronous operations.

Asynchronous execution of JavaScript
Overview
JavaScript language execution environment is "single thread" (single thread). The so-called "single-threaded" means that only one task can be completed at a time. If there are multiple tasks, they must be queued, the previous task is completed, and then the next task is executed.

The advantage of this mode is that it is relatively simple to implement and the execution environment is relatively simple; the disadvantage is that as long as one task takes a long time, the subsequent tasks must be queued up, which will delay the execution of the entire program. Common browsers are unresponsive (feigned death), often because a certain piece of Javascript code runs for a long time (such as an infinite loop), causing the entire page to be stuck in this place, and other tasks cannot be performed.

The JavaScript language itself is not slow. What is slow is reading and writing external data, such as waiting for Ajax requests to return results. At this time, if the other server does not respond slowly, or the network is not smooth, it will cause the script to stagnate for a long time.

To solve this problem, the Javascript language divides the execution modes of tasks into two types: Synchronous and Asynchronous. "Synchronous mode" is the traditional practice. The latter task waits for the end of the previous task, and then executes it. The execution order of the program is consistent and synchronized with the arrangement order of the tasks. This is often used for simple, fast operations that do not involve reading or writing.

The "asynchronous mode" is completely different. Each task is divided into two parts. The first part of the code contains the request for external data, and the second part of the code is written as a callback function, which contains the processing of the external data. After the first piece of code is executed, instead of executing the second piece of code immediately, the execution right of the program is handed over to the second task. Wait until the external data is returned, and then the system will notify the execution of the second piece of code. Therefore, the execution order of the program is inconsistent and asynchronous with the arrangement order of the tasks.

The following summarizes several methods of "asynchronous mode" programming. Understanding them will allow you to write JavaScript programs with better structure, better performance, and easier maintenance.

Callback function The
callback function is the most basic method of asynchronous programming.

Suppose there are two functions f1 and f2, the latter waiting for the execution result of the former.

f1();
f2();
In the above code, f2 must wait until f1 is executed before it can be executed.

If f1 is a time-consuming task, consider rewriting f1 and writing f2 as a callback function of f1.

function f1(callback) {
  setTimeout(function () {
    // f1's task code
    // ...
    callback();
  }, 0);
}
The execution code becomes as follows.

f1(f2);
In this way, we turn the synchronous operation into an asynchronous operation, and setTimeout(fn, 0) puts fn into the next round of event loop execution. f1 will not block the running of the program, which is equivalent to executing the main logic of the program first and delaying the execution of time-consuming operations.

The advantage of the callback function is that it is simple, easy to understand and deploy, but the disadvantage is that it is not conducive to the reading and maintenance of the code, and the high coupling (Coupling) between the various parts makes the program structure chaotic and the process difficult to track (especially when the callback function is nested) , and each task can only specify one callback function.

Event monitoring
Another way of thinking is to use an event-driven model. The execution of tasks does not depend on the order of the code, but on whether an event occurs.

Take f1 and f2 as an example. First, bind an event to f1 (the jQuery way of writing is used here).

f1.on('done', f2);
The above line of code means that when a done event occurs in f1, f2 is executed. Then, rewrite f1:

function f1(){
  setTimeout(function () {
    // f1's task code
    f1.trigger('done');
  }, 1000);
}
In the above code, f1.trigger('done' ) means that after the execution is completed, the done event is triggered immediately, thereby starting to execute f2.

The advantage of this method is that it is easier to understand, multiple events can be bound, multiple callback functions can be specified for each event, and it can be "decoupling", which is conducive to the realization of modularization. The disadvantage is that the entire program has to become event-driven, and the running process will become very unclear.

Publish/Subscribe
"Event" can be completely understood as "signal". If there is a "signal center" and a task is completed, it will "publish" a signal to the signal center, and other tasks can "subscribe" to the signal center. This signal, so as to know when it can start executing. This is called the "publish-subscribe pattern", also known as the "observer pattern".

There are several implementations of this pattern, the following is Tiny Pub/Sub by Ben Alman, a plugin for jQuery.

First, f2 subscribes the "done" signal to the "signal center" jQuery.

jQuery.subscribe("done", f2);
Then, f1 is rewritten as follows:

function f1(){
setTimeout(function () {
// f1's task code
jQuery.publish("done");
}, 1000);
}
jQuery.publish("done") means that after the execution of f1 is completed, the "done" signal is published to the "signal center" jQuery, thereby triggering the execution of f2.

After f2 finishes executing, you can also unsubscribe.

jQuery.unsubscribe("done", f2);
The nature of this method is similar to "event listener", but it is significantly better than the latter. Because we can monitor the operation of the program by looking at the "message center" to know how many signals exist and how many subscribers each signal has.

Flow Control for Asynchronous Operations
If there are multiple asynchronous operations, there is a problem of flow control: determine the order in which the operations are performed, and how to ensure that this order is followed in the future.

function async(arg, callback) {
  console.log('The parameter is ' + arg +' , the result will be returned after 1 second');
  setTimeout(function() { callback(arg * 2); }, 1000);
}
Above code The async function is an asynchronous task, which is very time-consuming, each execution takes 1 second to complete, and then the callback function is called.

If there are 6 such asynchronous tasks, all of them need to be completed before the final function of the next step can be executed.

function final(value) {
  console.log('Complete: ', value);
}
How should the operation flow be arranged?

async(1, function(value){
  async(value, function(value){
    async(value, function(value){
      async(value, function(value){
        async(value, function(value){
          async(value, final );
        });
      });
    });
  });
});
The above code uses the nesting of 6 callback functions, which is not only troublesome to write, error-prone, but also difficult to maintain.

Serial execution
We can write a flow control function to control asynchronous tasks, and after one task is completed, execute another. This is called serial execution.

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
function series(item) {
  if(item) {
    async( item, function(result) {
      results.push(result);
      return series(items.shift());
    });
  } else {
    return final(results);
  }
}
series(items.shift());
In the above code, the function series is a serial function, which executes asynchronous tasks in sequence , the final function will be executed only after all tasks are completed. The items array holds the parameters of each asynchronous task, and the results array holds the running result of each asynchronous task.

Parallel execution The
process control function can also be executed in parallel, that is, all asynchronous tasks are executed at the same time, and the final function is executed after all the tasks are completed.

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];

items.forEach(function(item) {
  async(item, function(result){
    results.push(result);
    if(results.length == items.length) {
      final(results);
    }
  } )
});
In the above code, the forEach method will initiate 6 asynchronous tasks at the same time, and the final function will not be executed until they are all completed.

The advantage of parallel execution is that it is more efficient, and it saves time compared to serial execution that can only execute one task at a time. But the problem is that if there are many parallel tasks, it is easy to exhaust system resources and slow down the running speed. So there is a third way of flow control.

The combination of parallel and serial The
so-called parallel and serial is to set a threshold, and only n asynchronous tasks can be executed in parallel at most each time. This avoids excessive occupation of system resources.

var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var limit = 2;

function launcher() {
  while(running < limit && items.length > 0) {
    var item = items.shift();
    async(item, function(result) {
      results.push(result);
      running--;
      if(items.length > 0) {
        launcher();
      } else if(running == 0) {
        final();
      }
    }) ;
    running++;
  }
}

launcher();
In the above code, at most two asynchronous tasks can be run at the same time. The variable running records the number of tasks currently running. As long as it is lower than the threshold value, a new task will be started. If it is equal to 0, it means that all tasks have been executed, and then the final function will be executed.

Promise object
Introduction
Promise object is a specification proposed by the CommonJS working group, the purpose is to provide a unified interface for asynchronous operations.

So, what are Promises?

First, it is an object, which means that it is used in the same way as other JavaScript objects; second, it acts as a proxy, acting as an intermediary between asynchronous operations and callback functions. It enables asynchronous operations to have a synchronous operation interface, so that the program has a normal synchronous operation process, and the callback functions do not need to be nested layer by layer.

Simply put, the idea is that each asynchronous task immediately returns a Promise object, and since it returns immediately, the process of synchronous operation can be adopted. This Promises object has a then method that allows specifying a callback function to be called when the asynchronous task completes.

For example, the asynchronous operation f1 returns a Promise object, and its callback function f2 is written as follows.

(new Promise(f1)).then(f2);
This is especially convenient for multiple nested callback functions.

// traditional writing
step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // ...
      });
    });
  } );
});

// Promises writing
(new Promise(step1))
  .then(step2)
  .then(step3)
  .then(step4);
As you can see from the above code, after using the Promises interface, the program flow becomes very Clear and very readable.

Note that for ease of understanding, the generation format of the Promise object in the above code has been simplified. Please refer to the following for the real syntax.

In general, the traditional way of writing callback functions makes the code cluttered and grows horizontally rather than downwardly. The Promises specification is proposed to solve this problem. The goal is to use the normal program flow (synchronous) to handle asynchronous operations. It first returns a Promise object, and the subsequent operations are registered on this object in a synchronous manner. Wait until the asynchronous operation has a result, and then perform other operations that were deposited on it earlier.

Promises were originally just an idea proposed by the community, and some external function libraries took the lead in implementing this function. ECMAScript 6 wrote it into the language standard, so the JavaScript language currently supports Promise objects natively.

Promise interface
As mentioned earlier, the basic idea of ​​the Promise interface is that an asynchronous task returns a Promise object.

Promise objects have only three states. There are only two ways to change the status of the asynchronous operation "

pending", the
asynchronous operation "resolved" (also known as fulfilled), and the
asynchronous operation "rejected" . Asynchronous operations go from "Incomplete" to "Completed" Asynchronous operations go from "Incomplete" to "Failed". This change can only happen once, once the current state becomes "completed" or "failed", it means that there will be no new state changes. Therefore, there are only two final outcomes of Promise objects. The asynchronous operation succeeds, the Promise object returns a value, and the state becomes resolved. The asynchronous operation fails, the Promise object throws an error, and the state changes to rejected.








The Promise object uses the then method to add a callback function. The then method can accept two callback functions, the first is the callback function when the asynchronous operation succeeds (resolved), and the second is the callback function (can be omitted) when the asynchronous operation fails (rejected). Once the state changes, the corresponding callback function is called.

// po is a Promise object
po.then(
  console.log,
  console.error
);
in the above code, the Promise object po uses the then method to bind two callback functions: the callback function console.log when the operation succeeds, and the console.log when the operation fails The callback function console.error (can be omitted). Both functions accept the value returned by the asynchronous operation as a parameter.

The then method can be chained.

po
  .then(step1)
  .then(step2)
  .then(step3)
  .then(
    console.log,
    console.error
  );
In the above code, once the state of po becomes resolved, the callback function specified by each then will be called in turn, and each step will not be executed until the previous step is completed. The callback functions console.log and console.error of the last then method have an important difference in usage. console.log only displays the return value of the callback function step3, while console.error can display any errors that occur in step1, step2, and step3. That is to say, suppose that step1 operation fails and an error is thrown, then step2 and step3 will not be executed again (because they are callback functions for successful operations, not callback functions for failed operations). The Promises object starts looking, and then the callback function when the first operation fails, which is console.error in the above code. That is to say, the error of Promises object is transitive.

From a synchronization perspective, the above code is roughly equivalent to the following form.

try {
  var v1 = step1(po);
  var v2 = step2(v1);
  var v3 = step3(v2);
  console.log(v3);
} catch (error) {
  console.error(error);
}
Promise's Generating
ES6 provides a native Promise constructor for generating Promise instances.

The following code creates a Promise instance.

var promise = new Promise(function(resolve, reject) {
  // code for asynchronous operation

  if (/* the asynchronous operation succeeded*/){
    resolve(value);
  } else {
    reject(error);
  }
});
The Promise constructor accepts a function as a parameter, and the two parameters of the function are resolve and reject. They are two functions, provided by the JavaScript engine, without having to deploy them yourself.

The role of the resolve function is to change the state of the Promise object from "uncompleted" to "successful" (that is, from Pending to Resolved), to be called when the asynchronous operation is successful, and to pass the result of the asynchronous operation as a parameter; reject The function of the function is to change the state of the Promise object from "uncompleted" to "failed" (that is, from Pending to Rejected), to be called when the asynchronous operation fails, and to pass the error reported by the asynchronous operation as a parameter.

After the Promise instance is generated, the then method can be used to specify the callback functions of the Resolved state and the Reject state respectively.

po.then(function(value) {
  // success
}, function(value) {
  // failure
});
Usage Analysis
Promise usage, simply put it in one sentence: use the then method to add a callback function. However, there are some subtle differences in different ways of writing, please see the following four ways of writing, what are the differences between them?

// Writing method one
doSomething().then(function () {
  return doSomethingElse();
});

// Writing 2
doSomething().then(function () {
  doSomethingElse();
});

// Writing 3
doSomething().then(doSomethingElse());

// Writing 4
doSomething( ).then(doSomethingElse);
In order to facilitate the explanation, the following four writing methods use the then method to connect a callback function finalHandler. The parameter of the finalHandler callback function of writing method 1 is the running result of the doSomethingElse function.

doSomething().then(function () {
  return doSomethingElse();
}).then(finalHandler);
The parameter of the finalHandler callback function of writing method 2 is undefined.

doSomething().then(function () {
  doSomethingElse();
  return;
}).then(finalHandler);
The parameter of the finalHandler callback function in the third method is the running result of the callback function returned by the doSomethingElse function.

doSomething().then(doSomethingElse())
  .then(finalHandler);
There is only one difference between writing method 4 and writing method 1, that is, doSomethingElse will receive the result returned by doSomething().

doSomething().then(doSomethingElse)
  .then(finalHandler);
Promise application
Loading image
We can write the image loading as a Promise object.

var preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    var image = new Image();
    image.onload = resolve;
    image.onerror = reject;
    image.src = path;
  });
};
Ajax operation
Ajax operation is a typical asynchronous operation, which is traditionally written as follows.

function search(term, onload, onerror) {
  var xhr, results, url;
  url = 'http://example.com/search?q=' + term;

  xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);

  xhr.onload = function (e) {
    if (this.status === 200) {
      results = JSON.parse(this.responseText);
      onload(results);
    }
  };
  xhr.onerror = function (e) {
    onerror(e);
  };

  xhr.send();
}

search("Hello World", console.log, console.error);
如果使用Promise对象,就可以写成下面这样。

function search(term) {
  var url = 'http://example.com/search?q=' + term;
  var xhr = new XMLHttpRequest();
  var result;

  var p = new Promise(function (resolve, reject) {
    xhr.open('GET', url, true);
    xhr.onload = function (e) {
      if (this.status === 200) {
        result = JSON.parse(this.responseText);
        resolve(result);
      }
    };
    xhr.onerror = function (e) {
      reject(e);
    };
    xhr.send();
  });

  return p;
}

search("Hello World").then(console.log, console.error);
加载图片的例子,也可以用Ajax操作完成。

function imgLoad(url) {
  return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', url);
    request.responseType = 'blob';
    request.onload = function() {
      if (request.status === 200) {
        resolve(request.response);
      } else {
        reject(new Error('Image load failed:' + request .statusText));
      }
    };
    request.onerror = function() {
      reject(new Error('A network error occurred'));
    };
    request.send();
  });
}
Summary
The advantage of the Promise object is that the callback The function has become a canonical chain writing method, and the program flow can be clearly seen. Its set of interfaces can implement many powerful functions, such as deploying a callback function for multiple asynchronous operations, uniformly specifying processing methods for errors thrown in multiple callback functions, and so on.

Moreover, it has a benefit that none of the previous three methods have: if a task has been completed, and then add a callback function, the callback function will be executed immediately. So, you don't have to worry about missing an event or signal. The disadvantage of this approach is that it is relatively difficult to write and understand.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327054342&siteId=291194637