ES6 has a new Promise reference type that allows you to elegantly define and organize asynchronous logic. The next few versions added mechanisms to define asynchronous functions using async and await keywords,
futures
Is a stand-in for a result that does not yet exist. All modern browsers support the ES6 contract, and many other browser apis are based on it.
Futures basis
Instantiated by the new operator, creating a new contract requires passing in an executor function as an argument.
let p = new Promise(() => {}); SyntaxError setTimeout(console.log, 0, p) is raised if no executor function is provided. // Promise <pending>Copy the code
1. Schedule state machine
A contract is a stateful object with three states:
- Pending;
- This is a big pity, which is sometimes called “resolving” and resolved.
- I don’t like it.
Pending is the initial state of a contract. In the pending state, the contract is oksettlesOn behalf of successcashBeing or representing failureRefused toState. No matter which state you settle in, it’s irreversible.The status of an agreement does not change once it switches from pending to honored or rejected. But also,An agreement that cannot be guaranteed must be removed from the pending state.
The state of the contract is private and cannot be detected directly by JS. Contracts deliberately encapsulate asynchronous behavior to isolate external synchronization code.
2. Resolve values, reasons for rejection, and term use cases
Contracts have two main uses. The first is to abstractly represent an asynchronous operation, and the status of the contract indicates whether the contract has completed. “Pending” — not yet started/in progress, “cashed” — successfully completed, “Rejected” — not successfully completed.
In some cases, this state machine is the most useful information a contract can provide, knowing the completion status of a piece of asynchronous code.
In other cases, an asynchronous operation encapsulated by a contract actually generates a value that the program can access when it expects the contract state to change. Accordingly, if the term is rejected, the program expects a reason for rejection when the term changes.
To support both 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 term as soon as the state switches to reject. Both the value and the reason are immutable references that contain the original value or object. Both are optional, and the default value is undefined.
Asynchronous code that executes when the contract reaches a certain settled state always receives this value or reason.
3. Perform functions to control the contract status
Because the contract state is private, it can only be manipulated internally. The internal operation is done in the executive function of the contract.
The executor function has two primary responsibilities: initializing the asynchronous behavior of the contract and controlling the final transition of state. Where the final transition of the control state is achieved by calling its two parameters. Both function arguments are usually named resolve () and reject (). Call the corresponding function to switch the state to the corresponding state. Also, calling Reject () throws an error.
The state transition is irrevocable regardless of whether resolve () or reject () is called, and continuing to modify the state silently fails.
To avoid being stuck in a pending state, you can add a timed exit function by using setTimeout to set a callback that will reject the date after 10 seconds anyway:
let p = new Promise((resolve, reject) => { setTimeout(reject, 10000); Reject () {reject ()}); setTimeout(console.log, 0, p); // Promise<pending> setTimeout(console.log, 11000, p); // (After 10s) Uncaught error // (After 11s) Promise <rejected>Copy the code
If the code in the executor has been resolved or rejected before the timeout, the timeout callback attempts to reject will also silently fail.
4. Promise.resolve()
A resolved term can be instantiated by calling the promise.resolve () static method. The following two contract instances are actually the same:
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
Copy the code
The value of the resolved term corresponds to the first argument passed to promise.resolve (), which can be converted from virtually any value to a term using this static method:
setTimeout(console.log, 0, Promise.resolve()); // Promise<resolved>: undefined setTimeout(console.log, 0, Promise.resolve(3)); SetTimeout (console.log, 0, promise.resolve (4,5,6)); // Promise<resolved>: 4Copy the code
For this static method, if the parameter passed is itself a term, it behaves like an empty wrapper. Y Therefore, this method can be said to be idempotent:
let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p)); // true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p))); // true
Copy the code
If the parameter is a non-scheduled value, it is cashed immediately. This static method can wrap any out-of-date value, including an error object, and convert it to a resolved term. Therefore, it can also lead to behavior that does not meet expectations.
5. Promise.reject()
Similar to promise.resolve (), promise.reject () instantiates a rejected term and throws an asynchronous error (this error can only be caught by a reject handler, not by a try/catch).
Let p1 = new Promise((resolve, reject) => reject())); let p2 = Promise.reject();Copy the code
The rejection reason is the first argument passed to promise.reject (), which is also passed to subsequent rejection handlers.
let p = Promise.reject(1003); setTimeout(console.log, 0, p); // Promise<rejected>: 3 p.then(null, (e) => setTimeout(console.log, 0, e)); / / 3Copy the code
Reject () does not have the idempotent logic of resolve (), and if it is passed a contract object, that date becomes the reason it returns a reject:
setTimeout(console.log, 0, Promise.reject(Promise.resolve()));
// Promise<rejected>: Promise<resolved>
Copy the code
Instance method of a contract
Is a bridge between external synchronous code and internal asynchronous code. These methods can access data returned by asynchronous operations, process the results of the contract’s successes and failures, evaluate the contract continuously, or add code that is executed only when the contract enters the terminating state.
1. Implement Thenable interface (understanding)
In the asynchronous architecture exposed by ES, any object has a then () method, which is thought to implement the Thenable interface. The ES Promise type implements the Thenable interface.
2. Promise. Prototype. Then ()
Is the primary method of adding handlers for approximately instances.
Receive two parameters: the onResolved handler and the onRejected handler. These parameters are optional and, if provided, will be executed when the contract enters the ‘honor’ and ‘reject’ states, respectively.
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 ")); // p1 resolved // P2 rejectedCopy the code
If the argument passed to then () is any non-function type handler, it is silently ignored. If you provide only the onRejected parameter, pass undefined in the onResolved parameter position.
P1. Then (' gobbeltygook); // Then (null, () => onRejected(' p2 ')); // Then (null, () => onRejected(' p2 ')); // p2 rejected (3 后)Copy the code
The promise.prototype.then () method returns a new contract instance:
let p1 = new Promise(() => {});
let p2 = p1.then();
setTimeout(console.log, 0, p1); // Promise pending
setTimeout(console.log, 0, p2); // Promise pending
setTimeout(console.log, 0, p1 === p2 ); // false
Copy the code
If the term of the new instance is built on the return value of the onResolved handler, promise.resolve () will wrap the value after the last term was resolved if no handler is provided. If no return statement is displayed, promise.resolve () wraps the default return value undefined. If there is a displayed return value, promise.resolve () wraps that value.
Throwing an exception returns the rejected contract.
. Let p10 = p1. Then (() => {throw 'baz'}); // Uncaught (in promise) baz setTimeout(console.log, 0, p10); // Promise<rejected> bazCopy the code
Note that returning an error value does not trigger the rejection action above, but wraps the error object in a resolution contract.
. Let p11 = p1. Then (() =>Error(' qux ')); setTimeout(console.log, 0, p11); // Promise<resolved> Error : quxCopy the code
The obRejected handler is similar: the value returned by the onRejected handler is also wrapped in promise.resolve ().
3. Promise.prototype.catch()
Used to add a reject handler to a contract that accepts only one parameter: the onRejected handler. In fact, this method is a syntactic sugar and calling it is equivalent to calling promise.prototype. then(null, onRejected).
let p = new Promise.reject(); Let onRejected = function(e){setTimeout(console.log, 0, 'rejected'); }; p.then(null, onRejected); // rejected p.catch(onRejected); // rejectedCopy the code
The promise.prototype.catch () method returns a new contract instance, behaving like the onRejected handler for promise.prototype.then ().
4. Promise.prototype.finally()
Use to add an onFinally handler to the contract. This handler is executed when the contract transitions to a resolved or rejected state. This method can avoid redundant code in the onResolved and onRejected handlers. The onFinally handler cannot know what the date status is and is used to add clean code.
Promise. Prototype. Finally () method returns a new issue about instances, is different from the former instance returned by the two ways. Because onFinally is a state-independent method, in most cases it will behave as a parent contract pass.
Let p1 = Promise. Resolve (" foo "); // let p2 = p1.finally(); let p3 = p1.finally(() => undefined); let p4 = p1.finally(() => Promise.resolve()); Let p5 = p1. Finally (() => promise.resolve (' bar ')); Let p6 = p1. Finally (() =>Error(' qux ')); setTimeout(console.log, 0, p2); // Promise<resolved> : foo setTimeout(console.log, 0, p3); // Promise<resolved> : foo setTimeout(console.log, 0, p4); // Promise<resolved> : foo setTimeout(console.log, 0, p5); // Promise<resolved> : foo setTimeout(console.log, 0, p6); // Promise<resolved> : fooCopy the code
If a pending term is returned, or if the onFinally handler throws an error (showing that a rejected term was thrown or returned), the corresponding term (pending or rejected) is returned.
. // promise.resolve () let p7 = p1.finally(() => new Promise(() => {})); let p8 = p1.finally(() => Promise.reject()); // Uncauguht (in promise) : undefined let p9 = p1. Finally (() => {throw 'baz'; }); // Uncauguht (in promise) : baz setTimeout(console.log, 0, p7); // Promise<pending> setTimeout(console.log, 0, p8); // Promise<rejected> : undefined setTimeout(console.log, 0, p9); // Promise<rejected> : bazCopy the code
5. Non-reentrant contract method
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 is the order of execution even if the contract is initially associated with the additional handler, and is referred to as a “non-reentrant” feature.
let p = Promise.resolve(); P.chen (() => console.log(' onResolved Handler ')); // Add console.log(' then() returns'); The output is as follows: // then() returns // onResolved handlerCopy the code
This handler does not execute until the synchronous code execution on the current thread is complete, so the synchronous code followed by then () must execute before the handler.
Non-reentrant applies to onResolved/onRejected handlers, catch () handlers and finally () handlers. The following handlers can only be executed asynchronously:
let p1 = Promise.resolve(); P1. Then (() = > console. The log (' p1. Then () onResolved ')); The console. The log (' after p1. Then () "); let p2 = Promise.reject(); P2.then (null, () => console.log(' p2.then() onRejected ')); The console. The log (' after p2. Then () "); let p3 = Promise.reject(); P3. Catch (() = > console. The log (' p3. Catch () onRejected ')); The console. The log (' after p3. Catch () "); let p4 = Promise.resolve(); P4. Finally (() = > console. The log (' p4. Finally () onFinally ')); The console. The log (' after p4. Finally () "); // after p1.then() after p2.then() after p3.catch() after p4.finally() // p1.then() onResolved p2.then() onRejected p3.catch() onRejected p4.finally() onFinallyCopy the code
6. Execution sequence of adjacent handlers
If you add more than one handler to a contract, when the contract state changes, the related handlers are executed in the order in which they were added, whether they are then (), catch (), or finally () handlers.
7. Pass resolution values and rejection reasons
After reaching the settled state, the appointment provides its resolution value (if cashed) or its reason for rejection (if rejected) to the handler of the relevant state. Once you have the return value, you can further manipulate the value.
In the execution function, the resolve value and the rejection reason are passed as the first arguments to resolve () and reject (), respectively. It then passes it 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(' err ')); p2.catch((reason) => console.log(reason)); // errCopy the code
Promise.resolve () and promise.reject () receive the resolution value and the rejection reason when they are called and, again, pass the value to the onResolved or onRejected handler as if it were an executor.
Let p1 = Promise. Resolve (" foo "); p1.then((value) => console.log(value)); // foo let p2 = promise. reject(' err '); p2.catch((reason) => console.log(reason)); // errCopy the code
8. Reject terms and reject error handling
Rejection contracts are similar to throw expressions in that throwing an error in the execution function or handler of a contract results in rejection, and the corresponding error object is now the reason for rejection.
Contracts can be rejected for any reason, including undefined, but it is best to use the wrong object uniformly.
When an error is thrown in a contract, because the error is actually thrown asynchronously from a message queue, it does not prevent the runtime from continuing to execute synchronous instructions:
Promise. Reject (' Error (" foo ") "); The console. The log (" xixi "); // xixi // Uncaught (in promise) Error: fooCopy the code
Asynchronous errors can only be caught through the asynchronous onRejected handler:
Promise. Reject (the Error (” foo “). The catch ((e) = > {});
It is still possible to use try/catch to catch errors in executing functions before resolving or rejecting a contract:
Let p = new Promise((resolve, reject) => {try{throw Error(' foo '); {}} the catch (e) resolve (" herculean task "); }); setTimeout(console.log, 0, p); // Promise<resolved> : clnCopy the code
The onRejected handlers for then () and catch () are semantically equivalent to try/catch. The point of both is to catch an error and isolate it without affecting logical execution. The task of the onRejected handler should be to return a resolution date after catching an asynchronous error. Asynchronous error handling:
New Promise((resolve, reject) => {console.log(' begin execution '); Reject (the Error (" err ")); }). The catch ((e) = > {the console. The log (' caught the error, e); }). Then (() => {console.log(' continue execution '); }); // begin execution // caught error : err // continue executionCopy the code
Term linkage and term synthesis
1. Term contract chain
That is, by concatenating terms one by one, the methods of each term instance (then (), catch (), and finally ()) return a new term object, which in turn has its own instance method, so that the concatenated method calls form what is called a “term chain.” Serialize asynchronous tasks so that each term is resolved after a certain amount of time:
function producePromise(str){ return new Promise((resolve, reject) => { console.log(str); setTimeout(resolve, 1000); }); } producePromise(' p1 executor '). Then (() => producePromise(' p2 executor ')). Then (() => producePromise(' p3 executor ')) .then(() => producePromise(' p4 Executor ')) // p1 Executor (1s later) // P2 executor(2s later) // P3 executor(3s later) // P4 After executor (4 s)Copy the code
2. The period about figure
Because a term can have any number of handlers, term chaining can build the structure of a directed acyclic graph. Each contract is a node in the graph, and the handlers added using the instance method are directed vertices. The direction of the diagram is the order in which contracts are resolved or rejected.
3. Promise.all () and promise.race ()
Two static methods that combine multiple instances of a term into a term, while the behavior of the synthesized late term depends on the behavior of the inner term.
* Promise. All ()
This static method creates a term that is resolved after a set of terms have all been resolved, receives an iterable and returns a new term:
let p1 = Promise.all([ Promise.resolve(), Promise.resolve() ]); Let the p2 = Promise. All ([3, 4]); Let p3 = promise.all ([]); let p3 = promise.all ([]); // Empty iterables are equivalent to promise.resolve () let p4 = promise.all (); // Invalid syntaxCopy the code
If at least one of the contained terms has a pending state, the composite term is also pending. If one of the contained terms has a reject state, the composite term is also rejected. A rejection will result in a final rejection.
let p = Promise.all([
Promise.resolve(),
Promise.reject(),
Promise.resolve()
]);
setTimeout(console.log, 0, p); // Promise<rejected>
// Uncaught (in promise) undefined
Copy the code
If all terms are successfully resolved, the solution values of the synthesized term are an array of all the solution values of the term, in iterator order.
If the term contract is rejected, the first term appointment will use its own reasons as the reason for the refusal of the composite term. Subsequent terms of rejection do not affect the grounds for rejection of the final terms. The synthesized date silently handles all rejection operations that contain the date.
// Although only the rejection reasons of the first contract will enter the rejection process, the rejection of the second contract will also be processed silently, Let p = promise.all ([promise.reject (3), new Promise((resolve, reject) => setTimeout(reject, 1000))]); p.catch((reason) => setTimeout(console.log, 0, reason)); // 3 // No unhandled errorsCopy the code
* Promise.race()
This static method returns a wrapped term, a mirror image of the first term resolved or rejected in a set of collections, receives an iterable and returns a new term.
Elements in an iterable are transformed by promise.resolve () for a duration of approximately. An empty iterable is equivalent to a new Promise(() => {}).
Promise.race() wraps its settlement value or rejection reason and returns the new term as long as it is the first settled term for a settled or rejected state. The order of iteration determines the order of termination.
If there is a term rejection, as long as it is the first one settled, it becomes a reason to reject the composite term. Subsequent terms of rejection do not affect the grounds for rejection of the final terms. Like promise.all (), the synthesized date silently handles all rejection operations that contain the date.
Cancellation of terms and progress notifications is not supported in ES6, mainly because of the excessive complexity of term chain and term composition.
Asynchronous functions (async/await)
Also called “async/await” (syntax keyword), is the application of ES6 contract pattern in ES functions. Async /await is new to the ES8 specification. ES extends the function with two new keywords: async and await.
1. async
For declaring asynchronous functions, you can use it on function declarations, function expressions, arrow functions, and methods:
async function foo() {}
let bar = async function() {};
let baz = async () => {};
class Tex{
async tex() {}
}
Copy the code
Overall, the code is still evaluated synchronously, and asynchronous functions still have the normal behavior of normal JavaScript functions in terms of arguments or closures. Foo () will still be evaluated before the following instruction:
async foo(){ console.log(1); } foo(); console.log(2); / / 1 / / 2Copy the code
If an asynchronous function returns a value using the return keyword (undefined if there is no return), the value is wrapped as a date object by promise.resolve (). Call this function outside the function to get the contract it returns:
async foo(){ console.log(1); return 3; // return Promise.resolve(3); } foo().then(console.log); // Add a resolution handler console.log(2) to the returned contract; // 1/2/3Copy the code
If an object is returned that implements the Thenable interface, it can be “unpacked” by the then () handler. If not, the return value is treated as the resolved term.
Async function foo(){// return original value return 'foo'; } foo().then(console.log); // foo async function baz(){const thenable = {then(callback) {callback(' baz '); }}; return thenable; } baz().then(console.log); // baz async function qux(){return promise.resolve (' qux '); } qux().then(console.log); // quxCopy the code
As in a contract handler, throwing an error in an asynchronous function returns a rejected contract. An error rejecting a contract is not caught by an asynchronous function.
async function bar(){
console.log(1);
throw 3;
}
bar().catch(console.log);
console.log(2);
// 1
// 2
// 3
async function foo(){
console.log(1);
Promise.reject(3);
}
foo().catch(console.log);
console.log(2);
// 1
// 2
// Uncaught (in promise) : 3
Copy the code
2. await
You can suspend the execution of asynchronous function code, and the wait period is resolved. You can use it alone or in an expression.
Async function foo(){console.log(await promise.resolve (' foo ')); async function foo(){console.log(await promise.resolve (' foo ')); } foo(); // foo async function bar(){return await promise.resolve (' bar '); } bar().then(console.log); // bar async function baz(){ await new Promise((resolve, reject) => setTimeout(resolve, 1000)); } baz(); // baz (1000ms later)Copy the code
If it is an object that implements the Thenable interface, the object can be “unpacked” by await. If not, the value is treated as the resolved term.
Async function foo(){// Wait for a raw value console.log(await 'foo'); } foo(); // foo async function baz(){const thenable = {then(callback) {callback(' baz '); }}; console.log(await thenable); } baz(); // baz async function qux(){console.log(await promise.resolve (' qux ')); } qux(); // quxCopy the code
Waiting for synchronization operations that throw errors returns rejected dates. To use await on a rejected term releases an error value (to return the rejected term).
async function foo(){ console.log(1); await(() => { throw 3; }); } foo().catch(console.log); console.log(2); // 1 // 2 // 3 async function bar(){ console.log(1); await Promise.reject(3); console.log(4); } bar().catch(console.log); console.log(2); // 1/2/3Copy the code
3. Limitation of await
Must be used in asynchronous functions, not in top-level contexts such as
(async function(){console.log(await promise.resolve (3)); {}}); / / 3Copy the code
Using await inside a synchronous function raises a SyntaxError.
Stop and resume execution
What really works in async/await is await. The JS runtime records where execution is paused when it encounters the await keyword. When the value to the right of the await is available, the JS runtime pushes a task to the message queue that resumes execution of the asynchronous function.
Even if await is followed by an immediately available value, the rest of the function is evaluated asynchronously.
async foo(){ console.log(2); await null; console.log(4); } console.log(1); foo(); console.log(3); // 1/2/3 // At this point, the code of the synchronous thread is finished, the JS runtime takes out the task from the message queue, and resumes the asynchronous function execution // 4Copy the code
If await is followed by a term, there are actually two tasks added to the message queue and evaluated asynchronously in order to execute the asynchronous function.
Asynchronous function strategy
- Implement sleep ()
async function sleep(delay){ return new Promise((resolve) => setTimeout(resolve, delay)); } async function foo(){ const t0 = Date.now(); await sleep(1500); // Pause about 1500ms console.log(date.now () -t0); } foo(); / / 1502Copy the code
- 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); / / 19Copy the code