JavaScript asynchronous programming of promise and async
Asynchronous programming
As we know, the JavaScript language executes in a “single-threaded” environment. The disadvantage of this model is that as long as one task takes a long time, the following tasks must wait in line. The common problem of unresponsive browsers is that a piece of JS code runs for a long time, causing the whole page to be stuck in this place, and other tasks can not be executed.
To solve this problem, the JavaScript language divides the execution mode of tasks into: Synchronous and Asynchronous. The synchronous mode is the one described above. Asynchronous mode is: each task has one or more callback functions (callback). When the previous task finishes, instead of executing the next task, the callback function is executed. The latter task is executed without waiting for the previous task to finish. The order of execution is inconsistent and asynchronous with the order in which the tasks are listed. For example, an Ajax operation that executes http requests asynchronously.
Common “asynchronous mode” programming
Callback functions
This is the most basic approach to asynchronous programming. If f1 is a very time-consuming task, consider rewriting f1 to write f2, which depends on the results returned by f1, as a callback function for f1. As follows:
1
2
3
4
5
6
7
8
9
10
11
function f1(callback) {
setTimeout(function() {
// f1 code
callback()
}, 1000)
}
function f2() {
// f2 code
}
f1(f2)
Pros: simple, easy to understand and deploy; Cons: the parts are highly coupled, the process is very confusing; each task can only develop a callback function.
Event listener
Event listening: the execution of a task does not depend on the order of the code, but on whether a certain event occurs or not. Modify the above writeup with this approach:
1
2
3
4
5
6
7
8
9
// jQuery的写法
f1.on('done', f2)
function f1() {
setTimeout(() => {
// f1 code
f1.trigger('done')
}, 1000)
}
Pros: easy to understand; can bind multiple events, each event can specify multiple callback functions; conducive to the realization of modularity Disadvantages: the entire program has to become event-driven, the operation process is not clear
Publish/Subscribe
Publish/Subscribe Pattern (Observer Pattern): Assuming the existence of a signal center, the completion of the execution of a task, like the signal center to publish a signal (event), other tasks can subscribe to the signal center to this signal (event), so as to know when they can begin to execute.
1
2
3
4
5
6
7
8
9
function f1() {
setTimeout(() => {
// f1 code
jQuery.publish('done') // 发布
}, 1000)
}
jQuery.subscribe('done', f2) // 订阅
// jQuery.unsubscribe('done', f2) // 取消订阅
Similar to, but better than, event listening. Benefits: You can monitor the operation of your program by looking at the message center to see how many signals exist and how many subscribers each signal has.
Promise object
The Promises object is a specification proposed by the CommonJS working group and written into the language standard by Es6, which provides the Promise object natively.Promise accepts as a parameter a function whose two arguments are resolve and reject, which are the callback functions.
- resolve function: called when the asynchronous operation succeeds, and will pass the result of the asynchronous operation as an argument;
- reject function: called when the asynchronous operation fails, and the asynchronous operation reported as an error, as a parameter to pass out.
After the Promise instance is generated, the then method can be used to specify callback functions for the resolved state and the reject state. Such as:
1
2
3
4
5
6
7
8
const f1 = new Promise((resolve, reject) => {
if (/* 异步操作成功 */) {
resolve('success')
} else {
reject('fail')
}
})
f1.then(f2)
Pros: callback functions become chained writes, making the program flow clear; if a task is already completed and a callback function is added, that callback function executes immediately, so you don’t have to worry about whether you’ve missed an event or signal.
Promise Basic API and FAQs
.then()
1
2
3
Promise.prototype.then(onFulfilled, onRejected)
promise.then(f2).then(f3)
Adding onFulfilled and onRejected callbacks to a Promise and returning is a new instance of the Promise and the return value will be passed as a parameter to the resolve function of this new Promise. So we can use chained writing. As in the example above.
.catch()
This method is used to specify the callback function when an error occurs. The reject method acts as the equivalent of throwing an error. Errors in promise objects are passed backwards until they are caught. That is, the error will always be caught by the next catch.
1
2
3
4
5
6
7
Promise.prototype.catch(onRejected)
promise.then(data => {
console.log(data)
}).catch((e) => {
console.log(e)
})
If the catch method is not used to specify a callback function that handles the error, errors thrown by the Promise object are not passed on to the outer code, i.e., there is no reaction (Chrome throws an error), which is another drawback of Promise.
.all()
This method is used to wrap multiple Promise instances into a new Promise instance.Promise.all method accepts an array (or has the Iterator interface) as a parameter, and the objects in the array (p1, p2, p3) are all Promise instances.
1
const p = Promise.all([p1, p2, p3])
- When the states of p1, p2, and p3 all become fulfilled, p’s state will also become fulfilled, and the results returned by the three promises will be stored in an array in the order of their arguments (not in the order of resolved), and passed to p’s callback function.
- When the state of one of p1, p2, or p3 becomes rejected, the state of p will also become rejected, and the return value of the first rejected promise will be passed to p’s callback function.
.race()
This method also takes multiple Promise instances and wraps them into a new Promise instance. Unlike all: when the state of one of p1, p2, p3 instances changes (to fullfilled or rejected), the state of p changes accordingly. And the return value of the first Promise that changed state is passed to p’s callback function.
1
const p = Promise.all([p1, p2, p3])
Some common questions
- the difference between reject and catch
- promise.then(onFulfilled, onRejected)
If an exception occurs in onFulfilled, it is not caught in onRejected.
- promise.then(onFulfilled).catch(onRejected)
Exceptions raised in .then can be caught in .catch. So this is the recommended way to write it
- If an error is thrown in then, and the error is not handled (i.e., caught), then it will remain in the REJECT state until the error is caught. For example, the following code is written in such a way as to ensure the normal execution of taskB
1
2
3
4
5
6
promise
.then(taskA)
.catch(onRejectedA)
.then(taskB)
.catch(onRejectedB)
.then(finalTask)
- Each call to
thenreturns a newly createdpromiseobject, and insidethenis just the returned data.
As in the code below, in the first method, then calls start executing almost simultaneously, and the value passed to each then is 100, which should be avoided. The second method is the correct chaining call.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//方法1:对同一个promise对象同时调用 then 方法
var p1 = new Promise(function (resolve) {
resolve(100);
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
return value * 2;
});
p1.then(function (value) {
console.log("finally: " + value);
});
// finally: 100
//方法2:对 then 进行 promise chain 方式进行调用
var p2 = new Promise(function (resolve) {
resolve(100);
});
p2.then(function (value) {
return value * 2;
}).then(function (value) {
return value * 2;
}).then(function (value) {
console.log("finally: " + value);
});
// finally: 400
- Throwing errors in asynchronous callbacks will not be caught by the
1
2
3
4
5
6
7
8
9
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
throw 'Uncaught Exception!' // 异步回调中抛错
}, 1000);
});
promise.catch(e => {
console.log(e); //This is never called
});
promisestate changes to remove or reject, it is frozen and will not change again
Async/Await
Promise allows us to say goodbye to callback functions and write more elegant asynchronous code. But in practice, we find that Promise has its imperfections.Async/Await was introduced in ES7, which enables us to write asynchronous code in a way that it becomes synchronous.
Rules
asyncmeans this is anasyncfunction, andawaitcan only be used inside this function.awaitmeans wait here for the operation immediately followingawaitto finish before executing the next line of code.awaitshould ideally be followed by a time-consuming operation or an asynchronous operation (of course, a non-time-consuming operation would be fine, but it would be meaningless).
Versus Promise
Here is an example of the Axios library sending a request to a GraphQL server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// promise写法
axios.get(`url`)
.then(response => response.data)
.then(data => {
f1(data)
})
.catch(error => console.log(error))
// Async/Await写法
async fetchData(url) => {
try {
const response = await axios.get(`url`)
const data = response.data
f1(data)
} catch (error) {
console.log(error)
}
}
As you can see, async/await, like Promise, is non-blocking. But async/await makes asynchronous code look like synchronous code, and that’s where the magic lies.