企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
The **`Promise`** object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value. 一个 Promise 对象代表着一个还未完成,但预期将来会完成的操作。主要使用它处理回调黑洞。 ---- 目录 [TOC] ---- ## Promise [[MDN-Using promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises)] A **[`Promise`]** is an object that's returned by a function that has not yet completed its work. The promise literally represents a promise made by the function that it will eventually return a result through the promise object. When the called function finishes its work asynchronously, a function on the promise object called a resolution (or fulfillment, or completion) handler is called to let the original caller know that the task is complete. Essentially, a **`Promise`** is a returned object to which you attach callbacks, instead of passing callbacks into a function. Example: ~~~js function successCallback(result) { console.log("Audio file ready at URL: " + result); } function failureCallback(error) { console.log("Error generating audio file: " + error); } createAudioFileAsync(audioSettings, successCallback, failureCallback); ~~~ use promise to improve: ~~~js const promise = createAudioFileAsync(audioSettings); promise.then(successCallback, failureCallback); ~~~ the shorthand for it: ~~~js createAudioFileAsync(audioSettings).then(successCallback, failureCallback); ~~~ ### constructor ~~~js var promise1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve('foo'); }, 300); }); promise1.then(function(value) { console.log(value); // expected output: "foo" }); console.log(promise1); // expected output: [object Promise] ~~~ A `Promise` is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a *promise* to supply the value at some point in the future. As the `Promise.prototype.then()` and `Promise.prototype.catch()` methods return promises, they can be chained. ### Promise States [[参考文档](https://promisesaplus.com/)] A promise must be in one of three states. 1. **`pending`**: initial state, neither fulfilled nor rejected. a promise may transition to either the `fulfilled` or `rejected` state. 2. **` fulfilled`**: meaning that the operation completed successfully. a promise * must not transition to any other state. * must have a value, which must not change. 3. **`rejected`**: meaning that the operation failed. a promise * must not transition to any other state. * must have a reason, which must not change. Here, “must not change” means immutable identity (i.e. `===`), but does not imply deep immutability. A pending promise can either be *fulfilled* with a value, or *rejected* with a reason (error). When either of these options happens, the associated handlers queued up by a promise's `then` method are called. (If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached.) ### Guarantees a promise comes with some guarantees: * Callbacks will never be called before the [completion of the current run](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Run-to-completion) of the JavaScript event loop. * Callbacks added with [`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) even *after* the success or failure of the asynchronous operation, will be called, as above. * Multiple callbacks may be added by calling [`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) several times. Each callback is executed one after another, in the order in which they were inserted. One of the great things about using promises is **chaining**. Because `then()` function returns a **new promise**, each promise represents the completion of another asynchronous step in the chain. example: ~~~js doSomething() .then(function(result) { return doSomethingElse(result); }) .then(function(newResult) { return doThirdThing(newResult); }) .then(function(finalResult) { console.log('Got the final result: ' + finalResult); }) .catch(failureCallback); ~~~ The arguments to `then` are optional, and `catch(failureCallback)` is short for `then(null, failureCallback)`. You might see this expressed with arrow functions instead: ~~~js doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => { console.log(`Got the final result: ${finalResult}`); }) .catch(failureCallback); ~~~ >[danger] Always return results, otherwise callbacks won't catch the result of a previous promise (with arrow functions `() => x` is short for `() => { return x; }`). Basically, a promise chain stops if there's an exception, looking down the chain for catch handlers instead. This is very much modeled after how synchronous code works: ~~~js try { const result = syncDoSomething(); const newResult = syncDoSomethingElse(result); const finalResult = syncDoThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); } ~~~ This symmetry with asynchronous code culminates in the `async`/`await` syntactic sugar in ECMAScript 2017: ~~~js async function foo() { try { const result = await doSomething(); const newResult = await doSomethingElse(result); const finalResult = await doThirdThing(newResult); console.log(`Got the final result: ${finalResult}`); } catch(error) { failureCallback(error); } } ~~~ **`Promises`** solve a fundamental flaw with the callback pyramid of doom, by catching all errors, even thrown exceptions and programming errors. This is essential for functional composition of asynchronous operations. ### Method #### **`Promise.resolve()`** The `Promise.resolve()` method returns a `Promise` object that is resolved with a given value. If the value is a: * `Promise`: that promise is returned; * 'thenable': (i.e. has a `"then" method`, the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value. This function flattens nested layers of promise-like objects (e.g. a promise that resolves to a promise that resolves to something) into a single layer. >[danger] **Warning**: Do not call `Promise.resolve` on a thenable that resolves to itself. This will cause infinite recursion as it tries to flatten what seems to be an infinitely nested promise. ~~~js Promise.resolve('Success').then(function(value) { console.log(value); // "Success" }, function(value) { // not called }); ~~~ #### **`Promise.reject()`** The `**Promise.reject()**` method returns a `Promise` object that is rejected with a given reason. The static `Promise.reject` function returns a `Promise` that is rejected. For debugging purposes and selective error catching, it is useful to make `reason` an `instanceof` [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error "The Error constructor creates an error object. Instances of Error objects are thrown when runtime errors occur. The Error object can also be used as a base object for user-defined exceptions. See below for standard built-in error types."). ~~~js Promise.reject(new Error('fail')).then(function() { // not called }, function(error) { console.log(error); // Stacktrace }); ~~~ #### **`Promise.all()`** The **`Promise.all()`** method returns a single `Promise` that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises. It rejects with the reason of the first promise that rejects. `Promise.all` is rejected if any of the elements are rejected. ~~~js var p1 = new Promise((resolve, reject) => { setTimeout(() => resolve('one'), 1000); }); var p2 = new Promise((resolve, reject) => { setTimeout(() => resolve('two'), 2000); }); var p3 = new Promise((resolve, reject) => { setTimeout(() => resolve('three'), 3000); }); var p4 = new Promise((resolve, reject) => { setTimeout(() => resolve('four'), 4000); }); var p5 = new Promise((resolve, reject) => { reject(new Error('reject')); }); // Using .catch: Promise.all([p1, p2, p3, p4, p5]) .then(values => { console.log(values); }) .catch(error => { console.log(error.message) }); //From console: //"reject" ~~~ #### **`Promise.race()`** The **`Promise.race()`** method returns a promise that resolves or rejects as soon as one of the promises in an iterable resolves or rejects, with the value or reason from that promise. * 语法/Syntax ~~~js Promise.race(iterable); ~~~ * 参数/Parameters `iterable` An iterable object, such as an `Array`. See [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/iterable). * 返回值/Return value A **pending** `Promise` that **asynchronously **yields the value of the first promise in the given iterable to resolve or reject. #### **`Promise.prototype.then()`** The **`then()`** method returns a `Promise`. It takes up to two arguments: callback functions for the success and failure cases of the `Promise`. * 语法/syntax ~~~js p.then(onFulfilled[, onRejected]); p.then((value) => { // fulfillment }, (reason) => { // rejection }); ~~~ 示例 ~~~js var p1 = new Promise( (resolve, reject) => { resolve('Success!'); // or // reject(new Error("Error!")); } ); p1.then( value => { console.log(value); // Success! }, reason => { console.log(reason); // Error! } ); ~~~ * 返回值/return value Once a `Promise` is fulfilled or rejected, the respective handler function (`onFulfilled` or `onRejected`) will be called **asynchronously** (scheduled in the current thread loop). The behavior of the handler function follows a specific set of rules. If a handler function: * returns a value, the promise returned by `then` gets resolved with the returned value as its value; * doesn't return anything, the promise returned by `then` gets resolved with an `undefined` value; * throws an error, the promise returned by `then` gets rejected with the thrown error as its value; * returns an already resolved promise, the promise returned by `then` gets resolved with that promise's value as its value; * returns an already rejected promise, the promise returned by `then` gets rejected with that promise's value as its value; * returns another **pending** promise object, the resolution/rejection of the promise returned by `then` will be subsequent to the resolution/rejection of the promise returned by the handler. Also, the value of the promise returned by `then` will be the same as the value of the promise returned by the handler. #### **`Promise.prototype.catch()`** The **catch()** method returns a `Promise` and deals with rejected cases only. It behaves the same as calling `Promise.prototype.then(undefined, onRejected)` (in fact, calling `obj.catch(onRejected)` internally calls `obj.then(undefined, onRejected)`). This means, that you have to provide `onRejected` function even if you want to fallback to `undefined` result value - for example `obj.catch(() => {})`. * 语法/Syntax ~~~javascript p.catch(onRejected); p.catch(function(reason) { // rejection }); ~~~ * 返回值Return value Internally calls `Promise.prototype.then` on the object upon which is called, passing the parameters `undefined` and the `onRejected` handler received; then returns the value of that call (which is a `Promise`). 示例: ~~~js var p1 = new Promise(function(resolve, reject) { resolve('Success'); }); p1.then(function(value) { console.log(value); // "Success!" throw new Error('oh, no!'); }).catch(function(e) { console.log(e.message); // "oh, no!" }).then(function(){ console.log('after a catch the chain is restored'); }, function () { console.log('Not fired due to the catch'); }); // The following behaves the same as above p1.then(function(value) { console.log(value); // "Success!" return Promise.reject('oh, no!'); }).catch(function(e) { console.log(e); // "oh, no!" }).then(function(){ console.log('after a catch the chain is restored'); }, function () { console.log('Not fired due to the catch'); }); ~~~ #### **`Promise.prototype.finally()`** The `**finally()**` method returns a `Promise`. When the promise is settled, i.e either fulfilled or rejected, the specified callback function is executed. This provides a way for code to be run whether the promise was fulfilled successfully or rejected once the `Promise` has been dealt with . This helps to avoid duplicating code in both the promise's `then()` and `catch()` handlers. The `finally()` method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome. The `finally()` method is very similar to calling `.then(onFinally, onFinally)` however there are couple of differences: * When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it * A `finally` callback will not receive any argument, since there's no reliable means of determining if the promise was fulfilled or rejected. This use case is for precisely when you *do not care* about the rejection reason, or the fulfillment value, and so there's no need to provide it. * Unlike `Promise.resolve(2).then(() => {}, () => {})` (which will be resolved with `undefined`), `Promise.resolve(2).finally(() => {})` will be resolved with `2`. * Similarly, unlike `Promise.reject(3).then(() => {}, () => {})` (which will be fulfilled with `undefined`), `Promise.reject(3).finally(() => {})` will be rejected with `3`. ~~~js let isLoading = true; fetch(myRequest).then(function(response) { var contentType = response.headers.get("content-type"); if(contentType && contentType.includes("application/json")) { return response.json(); } throw new TypeError("Oops, we haven't got JSON!"); }) .then(function(json) { /* process your JSON further */ }) .catch(function(error) { console.log(error); /* this line can also throw, e.g. when console = {} */ }) .finally(function() { isLoading = false; }); ~~~ ### Method Composition `Promise.resolve()` and `Promise.reject()`are shortcuts to manually create an already resolved or rejected promise respectively. This can be useful at times. `Promise.all()` and `Promise.race()` are two composition tools for running asynchronous operations in parallel. ### Timing functions passed to `then()` will never be called synchronously, even with an already-resolved promise,Instead of running immediately, the passed-in function is put on a microtask queue, which means it runs later when the queue is emptied at the end of the current run of the JavaScript event loop, ~~~js const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); wait().then(() => console.log(4)); Promise.resolve().then(() => console.log(2)).then(() => console.log(3)); console.log(1); // 1, 2, 3, 4 ~~~ ### Nesting Nesting is a control structure to limit the scope of `catch` statements. A nested `catch` only catches failures in its scope and below, not errors higher up in the chain outside the nested scope. ~~~js doSomethingCritical() .then(result => doSomethingOptional() .then(optionalResult => doSomethingExtraNice(optionalResult)) .catch(e => {})) // Ignore if optional stuff fails; proceed. .then(() => moreCriticalStuff()) .catch(e => console.log("Critical failure: " + e.message)); ~~~ The inner neutralizing `catch` statement only catches failures from `doSomethingOptional()` and `doSomethingExtraNice()`, after which the code resumes with `moreCriticalStuff()`. Importantly, if `doSomethingCritical()` fails, its error is caught by the final (outer) `catch` only. A good rule-of-thumb is to always either return or terminate promise chains, and as soon as you get a new promise, return it immediately, to flatten things: >[success] ~~~js > doSomething() > .then(function(result) { > return doSomethingElse(result); > }) > .then(newResult => doThirdThing(newResult)) > .then(() => doFourthThing()) > .catch(error => console.log(error)); >~~~ To provide a function with promise functionality, simply have it return a promise: ~~~js function myAsyncFunction(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = () => resolve(xhr.responseText); xhr.onerror = () => reject(xhr.statusText); xhr.send(); }); } ~~~