1. Asynchronous programming

Synchronous behavior corresponds to sequential execution of processor instructions in memory. Each instruction is executed in the exact order in which it appears, and each instruction is immediately followed by information stored locally in the system (such as registers or system memory).

Asynchronous behavior is similar to a system interrupt, where an entity outside the current process can trigger code execution.

Asynchronous operations are often necessary because it is usually not feasible to force a process to wait for a long operation (synchronous operations must wait). Long waits can occur if the code accesses resources with high latency, such as sending a request to a remote server and waiting for a response.

2, futures

ECMAScript 6’s new reference type Promise can be instantiated with the new operator. Creating a new contract requires passing in an executor function as an argument

2.1 Basis of the Term Agreement

The state machine

A contract is a stateful object that may be in one of three states:

  • Pending

  • This is a big pity, which is sometimes called “resolved” or “resolved”.

  • I don’t like it!

Pending is the initial state of a contract. This is a big pity, which is a pity. The appointment can be decided gradually, which is a big pity, which represents the successful fulfillment or the rejected state.

Resolve values, rejection reasons, and contract use cases

Each term contract has a private internal value whenever the state switches to cash. Similarly, there is a private internal reason for each contract as soon as the status switches to reject. Asynchronous code that executes when the contract reaches a certain settled state always receives this value or reason.

Control contract state by performing functions

The state of the contract is private, and internal operations are performed in the executive functions of the contract.

  • The transition of the control contract state is accomplished by calling its two function arguments (resolve() and reject())
  • The contract state transition can only occur once and cannot be revoked

Promise.resolve()

Call the promise.resolve () static method to instantiate a resolved term.

// These two expressions are equivalent
let p1 = new Promise((resolve, reject) = > resolve());
let p2 = Promise.resolve();
Copy the code

The first argument given to promise.resolve () corresponds to the resolved term, and any value can be converted to a term using this static method

setTimeout(console.log, 0.Promise.resolve());
// Promise <resolved>: undefined
setTimeout(console.log, 0.Promise.resolve(3));
// Promise <resolved>: 3
// Superfluous arguments are ignored
setTimeout(console.log, 0.Promise.resolve(4.5.6));
// Promise <resolved>: 4
Copy the code

PS: The passed parameter is itself a term, so it behaves like an empty wrapper. Thus, promise.resolve () can be said to be an idempotent method

Promise.reject()

Promise.reject() instantiates a reject term and throws an asynchronous error (which cannot be caught by a try/catch, but only by a reject handler).

2.2 Example method of term contract

Promise.prototype.then()

The then() method takes up to two arguments: the onResolved handler and the onRejected handler.

Both of these handler parameters are optional. Also, any non-functional arguments passed to then() are silently ignored. If you want to provide only the onRejected parameter, pass undefined in the onResolved parameter position.

function onResolved(id) {
 setTimeout(console.log, 0, id, 'resolved');
}
function onRejected(id) {
 setTimeout(console.log, 0, id, 'rejected');
}

let p1 = new Promise((resolve, reject) = > setTimeout(resolve, 3000));
let p2 = new Promise((resolve, reject) = > setTimeout(reject, 3000));

p1.then(() = > onResolved('p1'),
 		() = > onRejected('p1'));
p2.then(() = > onResolved('p2'),
 	    () = > onRejected('p2'));

// (3 seconds later)
// p1 resolved
// p2 rejected

// Non-function handlers are silently ignored and not recommended
p1.then('gobbeltygook');
// Do not pass the onResolved code
p2.then(null.() = > onRejected('p2'));

Copy the code

The promise.prototype.then () method returns a new contract instance. This new contract instance is built based on the return value of the onResovled handler.

Promise.prototype.catch()

The promise.prototype.catch () method is used to add a rejection handler to the contract.

This method accepts only one parameter: the onRejected handler.

In fact, this method is a syntactic sugar; calling it is equivalent to calling promise.prototype. then(null, onRejected).

Promise.prototype.finally()

Promise. Prototype. Finally add () method is used to issue about onFinally handler, the handler in the futures will perform when converted to solve or reject status.

But the onFinally handler has no way of knowing whether the date status is resolved or rejected, so this method is mostly used to add cleanup code.

Non-reentrant date reduction methods

When the contract enters the final state, the handlers associated with that state are only scheduled, not executed immediately. The synchronization code that follows the code that adds the handler must be executed before the handler. This feature is called the non-reentrant feature.

Non-reentrant applies to onResolved/onRejected handlers, catch() handlers and finally() handlers.

Pass resolution values and rejection reasons

In the execution function **, the resolved value and the reason for rejection are ** passed as the first argument to resolve() and reject(), respectively. These values are then passed to their respective handlers as the only argument to the onResolved or onRejected handlers.

let p1 = new Promise((resolve, reject) = > resolve('foo'));
p1.then((value) = > console.log(value)); // foo
let p2 = new Promise((resolve, reject) = > reject('bar'));
p2.catch((reason) = > console.log(reason)); // bar
Copy the code

Reject terms and reject error handling

Rejection contracts are similar to throw() expressions in that they represent a program state that requires interruption or special processing.

2.3 Term contract linkage and term contract synthesis

Futures chain

Concatenating schedules one by one is a very useful programming pattern.

This is because each contract instance’s methods (then(), catch(), and finally()) return a new contract object, which in turn has its own instance method. In this way, concatenated method calls can form what is called a “contract chain”.

let p1 = new Promise((resolve, reject) = > {
 console.log('p1 executor');
 setTimeout(resolve, 1000);
});
p1.then(() = > new Promise((resolve, reject) = > {
 console.log('p2 executor');
 setTimeout(resolve, 1000);
 }))
 .then(() = > new Promise((resolve, reject) = > {
 console.log('p3 executor');
 setTimeout(resolve, 1000);
 }))
 .then(() = > new Promise((resolve, reject) = > {
 console.log('p4 executor');
 setTimeout(resolve, 1000);
 }));
// p1 executor (1 second later)
// p2 executor (2 seconds later)
// p3 executor (3 seconds later)
// p4 executor (4 seconds later)
Copy the code

Promise. All () and Promise. Race ()

The Promise class provides two static methods for combining multiple contract instances into a single contract: promise.all () and promise.race ().

Promise.all()

Promise.all() Static methods create appointments that are resolved after a set of appointments has been resolved. This static method takes an iterable (typically a term object) and returns a new term.

If all terms are successfully resolved, the resolution values of the synthesized term are all arrays containing the resolution values of the term, in iterator order:

/ / promise syntax
let p = Promise.all([
 Promise.resolve(3),
 Promise.resolve(),
 Promise.resolve(4)]);// The solution values of the composite contract are all the arrays containing the solution values of the contract
p.then((values) = > setTimeout(console.log, 0, values)); // [3, undefined, 4]
Copy the code

Promise.all() static method, if at least one contained term is pending, then the composite term is also pending. If an contained term is rejected, then the composite term is also rejected:

// To be determined forever
let p1 = Promise.all([new Promise(() = >{}));setTimeout(console.log, 0, p1); // Promise <pending>

// A rejection will result in a final rejection
let p2 = Promise.all([
 Promise.resolve(),
 Promise.reject(),
 Promise.resolve()
]);
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught (in promise) undefined
Copy the code

Promise.race()

The promise.race () static method returns a wrapper term that is a mirror image of the first term resolved or rejected in a set of collections.

Promise.race() does not discriminate between resolved or rejected terms. Promise.race() wraps its settlement value or rejection reason for the first settled term, either resolved or rejected, and returns the new term:

// Resolve occurs first, rejection after timeout is ignored
let p1 = Promise.race([
 Promise.resolve(3),
 new Promise((resolve, reject) = > setTimeout(reject, 1000)));setTimeout(console.log, 0, p1); // Promise <resolved>: 3
Copy the code

3. Asynchronous functions

Asynchronous functions, also called “async/await” (syntax keyword), are ES6 contract patterns applied to ECMAScript functions. Async /await is designed to solve the problem of organizing code with asynchronous structures.

3.1 async

The async keyword is used to declare asynchronous functions. This keyword can be used for function declarations, function expressions, arrow functions, and methods.

Async allows functions to be asynchronous, but overall the code is still evaluated synchronously.

An asynchronous function that returns a value using the return keyword (or undefined if there is no return) is wrapped as a date object by * promise.resolve ()*.

async function foo() {
 return 1;
}
// Add a resolution handler to the returned contract
foo().then(console.log); 
/ / 1
Copy the code

Throwing an error in an asynchronous function returns a rejected contract :(rejected contract errors are not caught by an asynchronous function)

async function foo() {
 console.log(1);
 throw 3;
}
// Add a rejection handler to the returned contract
foo().catch(console.log);
console.log(2);
/ / 1
/ / 2
/ / 3
Copy the code

3.2 await

The await keyword suspends the code following the asynchronous function, freeing up the JavaScript runtime thread of execution.

The await keyword is also an attempt to “unpackage” the value of an object, pass the value to an expression, and asynchronously resume the execution of an asynchronous function.

The await keyword suspends the execution of asynchronous function code and the wait period is resolved:

async function foo() {
 let p = new Promise((resolve, reject) = > setTimeout(resolve, 1000.3));
 console.log(await p);
}
foo();
/ / 3
Copy the code

The await keyword must be used in asynchronous functions, not in top-level contexts such as

3.3 Stopping and Resuming execution

What really works in async/await is await. The async keyword, for all intentness, is nothing more than an identifier.

The await keyword is not as simple as waiting for a value to be available. The JavaScript runtime records where execution is paused when it encounters the await keyword. When the value to the right of the await is available, the JavaScript runtime pushes a task to the message queue that resumes the execution of the asynchronous function.

When await is followed by an immediately available value, the rest of the function is also evaluated asynchronously. The following example demonstrates this:

async function foo() {
 console.log(2);
 await null;
 console.log(4);
}
console.log(1);
foo();
console.log(3);
/ / 1
/ / 2
/ / 3
/ / 4
Copy the code

If await is followed by a term, the problem is slightly more complicated. At this point, two tasks are actually added to the message queue and evaluated asynchronously in order to execute the asynchronous function:

async function foo() {
 console.log(2);
 console.log(await Promise.resolve(8));
 console.log(9);
}
async function bar() {
 console.log(4);
 console.log(await 6);
 console.log(7);
}
console.log(1);
foo();
console.log(3);
bar();
console.log(5);
/ / 1
/ / 2
/ / 3
/ / 4
/ / 5
/ / 6
/ / 7
/ / 8
/ / 9

Copy the code

The runtime executes the above example like this:

(1) Print 1; (2) Call the asynchronous function foo(); (3) (in foo()) prints 2; (4) (in foo()) await keyword suspends execution and adds a task to the message queue scheduled to be executed after the execution; (5) The term is immediately terminated and the task providing the await value is added to the message queue; (6) Foo () exits; (7) Print 3; (8) Call the asynchronous function bar(); (9) (in bar()) print 4; (10) (in bar()) the await keyword is paused to add a task to the message queue for the immediately available value 6; (11) bar() exits; (12) Print 5; (13) The top-level thread has completed execution; (14) The JavaScript runtime takes the handler resolving the await period from the message queue and provides it with the resolved value 8; (15) The JavaScript runtime adds a task to the message queue to restore the execution of foo(); (16) the JavaScript runtime retrieves the task of executing bar() from the message queue and the value 6; (17) (in bar()) resume execution, await value 6; (18) (in bar()) print 6; (19) (in bar()) print 7; (20) bar () return; (21) The asynchronous task is complete and JavaScript is fetched from the message queue to resume the task executing foo() with the value 8; (22) (in foo()) prints 8; (23) (in foo()) prints 9; (24) foo() returns.

3.4 Asynchronous Function Policy

Serial execution date

With async/await, contract chaining is simple:

async function addTwo(x) {return x + 2; }async function addThree(x) {return x + 3; }async function addFive(x) {return x + 5; }async function addTen(x) {
 for (const fn of [addTwo, addThree, addFive]) {
 x = await fn(x);
 }
 return x;
}
addTen(9).then(console.log); / / 19
Copy the code