The profile
- The best way to learn a technology is to understand how it was created and what problems it solves.
- The following will be introduced from the following aspects
Promise
:- Problems with asynchronous programming: discontinuous code logic;
- Callback hell: too many nested callback functions;
Promise
: Kills nested calls;Promise
: Error handling for merging multiple tasks;Promise
Relationship with microtask;
- To be clear, promises address asynchronous coding styles, not some other issue;
The problem with asynchronous programming: Discontinuous code logic
- Suppose there is a request, use
XMLHttpRequest
To implement, the code is as follows:// Execution status function onResolve(response) { console.log(response); } function onReject(error) { console.log(error); } let xhr = new XMLHttpRequest(); xhr.ontimeout = function(e) { onReject(e); }; xhr.onerror = function(e) { onReject(e); }; xhr.onreadystatechange = function () { onResolve(xhr.response); }; // Set the request type, request URL, and whether to synchronize information let URL = 'https://localhost:8080/getList'; xhr.open('Get', URL, true); // Set parameters xhr.timeout = 3000; // Set the timeout period for XHR requests xhr.responseType = "text"; // Format the data to be returned in the response xhr.setRequestHeader("X_TEST"."time.geekbang"); // Make a request xhr.send(); Copy the code
- So there are five callbacks in this short piece of code, so many callbacks will lead to the logic of the code is not coherent, not linear, very intuitive, this is how asynchronous callbacks affect our coding;
Callback hell: Too many nested callback functions
-
It is also possible to wrap the above asynchronous code into a function, which can be called by passing in the corresponding request parameters, callback function, so that the processing flow becomes linear, like the following code:
// Request, request information, request header, delay value, return type, etc // resolve, execute successfully, callback this function // reject fails to execute, callback this function function XFetch(request, resolve, reject) { let xhr = new XMLHttpRequest(); xhr.ontimeout = function (e) { reject(e); }; xhr.onerror = function (e) { reject(e); }; xhr.onreadystatechange = function () { if (xhr.status = 200) resolve(xhr.response); }; xhr.open(request.method, request.url, request.sync); xhr.timeout = request.timeout; xhr.responseType = request.responseType; // Add additional request information // ... xhr.send(); } // Call wrapped asynchronous code XFetch( { method: 'GET'.url: 'https://localhost:8080/getList'.sync: true }, function resolve(data) { console.log(data); }, function reject(e) { console.log(e); });Copy the code
-
Encapsulating asynchronous code is fine for some simple logic, but if the logic is more complex, it can create a callback hell by nesting a lot of callback functions, like this:
XFetch( { method: 'GET'.url: 'https://localhost:8080/getList'.sync: true }, function resolve(response) { console.log(response); XFetch( { method: 'GET'.url: 'https://localhost:8080/getList1'.sync: true }, function resolve(response) { console.log(response); XFetch( { method: 'GET'.url: 'https://localhost:8080/getList2'.sync: true }, function resolve(response) { console.log(response); }, function reject(e) { console.log(e); })},function reject(e) { console.log(e); })},function reject(e) { console.log(e); });Copy the code
-
The above code looks messy for two reasons:
- Nested calls: The next task relies on the result of the previous task’s request and executes the new business logic inside the callback function of the previous task. As the nesting level increases, the code becomes very unreadable.
- Task uncertainty: Executing each task has two possible outcomes (success or failure), so an additional error handling for each task significantly increases the level of code clutter;
-
After analyzing the reasons, these two problems need to be solved:
- Eliminate nested calls;
- Error handling for merging multiple tasks;
-
ES6 introduced Promise to solve these two problems, which are described below.
Promise
: Kills nested calls
Promise solves the nested callback problem primarily in two steps:
-
Promise implements delayed binding of callback functions:
// Create the Promise object promise1 and execute the business logic in the executor function function executor(resolve, reject){ resolve(100); } let promise1 = new Promise(executor); // promise1 deferred binding callback onResolve function onResolve(value){ console.log(value); } promise1.then(onResolve); Copy the code
- As shown above:
- The delayed binding of the callback function is created first in the code
Promise
objectpromise1
; - through
Promise
Constructor ofexecutor
To execute the business logic; - To create a good
Promise
objectpromise1
After that, use it againpromise1.then()
To set the callback function;
- The delayed binding of the callback function is created first in the code
- In short, Promise implements the delayed binding of callback functions, separating the original callback writing method, and executing the callback function in the way of chain call after the asynchronous operation is completed.
- As shown above:
-
Promise penetrates the return value of the callback function onResolve to the outermost layer:
// Create the Promise object promise1 and execute the business logic in the executor function function executor(resolve, reject) { resolve(100); } const promise1 = new Promise(executor); // promise1 deferred binding callback onResolve function onResolve(value) { console.log(value); function executor2(resolve, reject) { resolve(value + 1); } return new Promise(executor2); } // promise2 penetrates to the outermost layer for the inner return value const promise2 = promise1.then(onResolve); promise2.then((value) = > { console.log(value); }); Copy the code
- As shown above, you can get rid of the nested loop by returning the Promise object created inside onResolve to the outermost layer;
Promise
: Error handling for merging multiple tasks
function executor(resolve, reject) {
let rand = Math.random();
console.log(1);
console.log(rand);
if (rand > 0.5)
resolve();
else
reject();
}
var p0 = new Promise(executor);
var p1 = p0.then((value) = > {
console.log("succeed-1");
return new Promise(executor);
});
var p3 = p1.then((value) = > {
console.log("succeed-2");
return new Promise(executor);
});
var p4 = p3.then((value) = > {
console.log("succeed-3");
return new Promise(executor);
});
p4.catch((error) = > {
console.log("error");
})
console.log(2);
Copy the code
- Above code, chain calls four
Promise
Object:P0 ~ p4
.Any object that throws an exception can pass through the last objectp4.catch
To catch exceptions; - In this way you can put all
Promise
Object errors are consolidated into a single function, thus eliminating the need for each task to handle exceptions separately; - The last object can be used to catch all exceptions because errors on the Promise object are “bubbling” and passed backwards until they are processed by the onReject function or caught by a catch statement.
- With this “bubbling” feature, there is no need for each
Promise
An exception is caught separately in the
Promise
Relationship to microtasks
function executor(resolve, reject) {
resolve(100)}let demo = new Promise(executor)
function onResolve(value){
console.log(value)
}
demo.then(onResolve)
Copy the code
-
Code like this:
- Executed first
new Promise
When,Promise
The constructor is executed; - Next,
Promise
The constructor of thePromise
The parameters of theexecutor
Functions; - Then, in
executor
To perform theresolve
. - perform
resolve
Function, will firedemo.then
Set the callback functiononResolve
;- So presumably,
resolve
The function is called internally throughdemo.then
Set up theonResolve
Functions;
- So presumably,
- Executed first
-
Note: Since Promise uses the callback function deferred binding technology, the callback function is not bound when the resolve function is executed, so the callback function can only be delayed.
-
For ease of understanding, a simple Promise object is implemented:
function Bromise(executor) { var onResolve_ = null; var onReject_ = null; // Implement resolve and then this.then = function (onResolve, onReject) { onResolve_ = onResolve; }; function resolve(value) { //setTimeout(()=>{ onResolve_(value); / /}, 0) } executor(resolve, null); } Copy the code
-
Call the Bromise object defined above:
function executor(resolve, reject) { resolve(100); } / / call Bromsie let demo = new Bromise(executor); function onResolve(value) { console.log(value); } demo.then(onResolve); Copy the code
- An error is reported because we are executing
executor
Function, it hasn’t passed yetdemo.then()
Set the callback function,Bromise
In theonResolve_
It’s still empty, so it’s an error;
- An error is reported because we are executing
-
This is where you need to modify the resolve method in Bromise to defer calling onResolve_ :
- To make the
resolve
In theonResolve_
Function deferred execution, can be inresolve
Function inside add a timer, let its delay executiononResolve_
Function, code as follows:function Bromise(executor) { var onResolve_ = null; var onReject_ = null; // Implement resolve and then this.then = function (onResolve, onReject) { onResolve_ = onResolve; }; function resolve(value) { // Use the setTimeout timer to delay the execution of the onResolve_ function setTimeout(() = > { onResolve_(value); }, 0); } executor(resolve, null); } Copy the code
- It uses a timer to delay
onResolve
But the efficiency of using timer is not too high;
- To make the
-
Therefore, Promise transforms this timer into a microtask, which not only allows onResolve_ to be called delayed, but also improves the execution efficiency of the code. This is why Promise uses microtasks.
Geeky time: Say goodbye to the callback function