preface
Promise is pretty familiar, considering just a few apis, but do you really know Promise?
This article will take you to a thorough inventory of promise~
Introduction of Promise
Promises are a way to handle asynchronous code without falling into callback hell.
Promise has been part of the language for years (standardized and introduced in ES2015) and has recently become more integrated with async and await in ES2017.
Asynchronous functions use promises underneath, so understanding how promises work is fundamental to understanding async and await.
The Promise object represents an asynchronous operation with three states: Pending, fulfilled and Rejected.
A Promise must be in one of the following states:
- To be determined
(pending)
: Initial state, neither honored nor rejected. - Has been successfully
(fulfilled)
: indicates that the operation completed successfully. - Has refused to
(rejected)
: Indicates that the operation fails.
How Promise works
When a promise is invoked, it starts as a pending state. This means that the called function continues to execute, and the promise remains in processing until resolved, providing whatever data is requested for the called function.
The promise that is created will eventually end with a state of fulfilled or rejected, and when it is fulfilled, the corresponding callback functions (passed to then and catch) will be called.
◾ To give readers a quick overview of promise, let’s take a look at the promise code:
let p1 = new Promise((resolve, reject) = > {
resolve('success')
reject('failure')})console.log('p1', p1)
let p2 = new Promise((resolve, reject) = > {
reject('failure')
resolve('success')})console.log('p2', p2)
let p3 = new Promise((resolve, reject) = > {
throw('error')})console.log('p3', p3)
Copy the code
The output is:
There are four points of knowledge:
- 1. Implemented
resolve
, the Promise state becomesfulfilled
, i.e.,Completed state - 2. Executed
reject
, the Promise state becomesrejected
, i.e.,Rejected state - 3, Promise only
First time prevail
The first successpermanent
forfulfilled
The first failure is foreverrejected
- 4. Promise
throw
If you do, you’re doing itreject
◾ Next look at the following code to learn new knowledge:
let myPromise1 = new Promise(() = > {});
console.log('myPromise1 :>> ', myPromise1);
let myPromise2 = new Promise((resolve, reject) = > {
let a = 1;
for (let index = 0; index < 5; index++) { a++; }})console.log('myPromise2 :>> ', myPromise2)
myPromise2.then(() = > {
console.log("MyPromise2 enforces then");
})
Copy the code
The output is:
There are three things:
- 1. The initial state of Promise is
pending
- 2. The Promise was not implemented
resolve
,reject
As well asthrow
Student: Well, thisSo is the promise statepending
- 3. Based on the previous article,
pending
A promise in a state does not execute a callback functionthen()
◾ One last point:
let myPromise0 = new Promise(a);console.log('myPromise0 :>> ', myPromise0);
Copy the code
Output result:
There is a point in this:
- It has to be given
Promise
Object passes an execution function, otherwise an error is reported.
Unlike the “old fashioned” incoming callback, when using promises, there are the following conventions:
- The callback is not called until the current event loop is complete.
- Even after the asynchronous operation has completed (successfully or failed), the callback function added via THEN () will be called.
- Multiple callbacks can be added by calling THEN () multiple times, and they are executed in the order they were inserted.
One of the cool things about Promise is the chain call.
Create a promise
The Promise API exposes a Promise constructor that can be initialized with new Promise() :
let done = true
const isItDoneYet = new Promise((resolve, reject) = > {
if (done) {
const workDone = 'This is what I created.'
resolve(workDone)
} else {
const why = 'Still working on other things'
reject(why)
}
})
Copy the code
As you can see, the promise checks for the done global constant, and if true, the promise goes into the resolved state (because the resolve callback was called); Otherwise, the reject callback is executed (putting the promise in a rejected state). If one of these functions has never been called in the execution path, the promise remains in processing state.
Use resolve and Reject to communicate the final promise state to the caller and what to do with it. In the example above, only one string is returned, but it can be an object or NULL. Since the Promise has been created in the code snippet above, it has already started executing.
A more common example is a technology known as Promisifying. This technique uses classic JavaScript functions to accept callbacks and make them return promises:
const fs = require('fs')
const getFile = (fileName) = > {
return new Promise((resolve, reject) = > {
fs.readFile(fileName, (err, data) = > {
if (err) {
reject(err) // Calling 'reject' causes the promise to fail, regardless of whether an error is passed as an argument,
return // And will not continue.
}
resolve(data)
})
})
}
getFile('/etc/passwd')
.then(data= > console.log(data))
.catch(err= > console.error(err))
Copy the code
The use of promise
In the previous section, you saw how to create promises.
Now, take a look at how to use promise.
const isItDoneYet = new Promise(/ *... As mentioned above... * /)
/ /...
const checkIfItsDone = () = > {
isItDoneYet
.then(ok= > {
console.log(ok)
})
.catch(err= > {
console.error(err)
})
Copy the code
Running checkIfItsDone() specifies the function to execute when an isItDoneYet Promise is resolved (in the THEN call) or rejected (in the catch call).
The instance Promise encapsulates AJAX
// Promise encapsulates Ajax requests
function ajax(method, url, data) {
var xhr = new XMLHttpRequest();
return new Promise(function (resolve, reject) {
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) return;
if (xhr.status === 200) {
resolve(xhr.responseText);
} else{ reject(xhr.statusText); }}; xhr.open(method, url); xhr.send(data); }); }Copy the code
Make a request using the ajax encapsulated above:
ajax('GET'.'/api/categories').then(function (data) {
// AJAX succeeds, get the response data
console.log(data);
}).catch(function (status) {
// AJAX failed. Determine the cause of the failure according to the response code
new Error(status)
});
Copy the code
Promise.resolve()
Sometimes you need to turn an existing object into a Promise object, and the promise.resolve () method does this.
Note that ❗ ❗ ❗ here promise.resolve () is used directly, not as the resolve() method in the Promise object, promise.resolve (), capitalized, not a Promise
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
Copy the code
This code converts the jquery-generated object into a new Promise object.
Promise.resolve() is equivalent to the following.
Promise.resolve('foo')
/ / equivalent to the
new Promise(resolve= > resolve('foo'))
Copy the code
The parameters of the promise.resolve method are divided into four cases.
The ◾ (1) argument is an instance of a Promise
If the argument is a Promise instance, promise.resolve will return the instance unchanged.
The ◾ (2) argument is a Thenable object
Thenable objects refer to objects that have then methods, such as this one.
let thenable = {
then: function(resolve, reject) {
resolve(42); }};Copy the code
The promise. resolve method converts this object to a Promise, and then immediately executes the thenable object’s then method.
let thenable = {
then: function(resolve, reject) {
resolve(42); }};let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); / / 42
});
Copy the code
As soon as the thenable object’s THEN method executes, object P1 will be in the resolved state and the callback specified by the last THEN method will execute immediately, printing 42.
The ◾ (3) argument is not an object with a then method, or is not an object at all
If the parameter is a raw value, or an object that does not have a then method, the promise.resolve method returns a new Promise object with the state Resolved.
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
Copy the code
The code above generates a new instance P of the Promise object. Since the string Hello is not an asynchronous operation (the string object does not have the then method), the return Promise instance state from lifetime achievement is Resolved, so the callback will execute immediately. The arguments to the promise. resolve method are also passed to the callback function.
◾ (4) does not take any parameters
The promise.resolve () method allows a call with no arguments to return a Resolved Promise object.
So, if you want a Promise object, the convenient way to do this is to call the promise.resolve () method directly.
const p = Promise.resolve();
p.then(function () {
// ...
});
Copy the code
The variable P in the code above is a Promise object.
Note that the Promise object for resolve() now is executed at the end of this event loop, not at the beginning of the next.
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
Copy the code
In the code above, setTimeout(fn, 0) is executed at the start of the next event loop, promise.resolve () is executed at the end of the current event loop, and console.log(‘one’) is executed immediately, so it is printed first.
Promise.reject()
The promise.Reject (Reason) method also returns a new Promise instance with a state of Rejected.
const p = Promise.reject('Wrong');
/ / is equivalent to
const p = new Promise((resolve, reject) = > reject('Wrong'))
p.then(null.function (s) {
console.log(s)
});
/ / make a mistake
Copy the code
The above code generates an instance P of the Promise object in the rejected state, and the callback is executed immediately.
Note that the arguments to the promise.reject () method are left as reject arguments to subsequent methods. This is inconsistent with the promise.resolve method.
const thenable = {
then(resolve, reject) {
reject('Wrong'); }};Promise.reject(thenable)
.catch(e= > {
console.log(e === thenable)
})
// true
Copy the code
Reject takes a Thenable object as the argument to the promise. reject method, which takes a Thenable object instead of the “something’s wrong” string thrown by Reject.
Promise.prototype.then()
Promise instances have THEN methods, that is, then methods defined on the prototype object Promise.Prototype. It adds a callback function to the Promise instance when the state changes. As mentioned earlier, the first argument to the THEN method is the callback in the Resolved state and the second argument (optional) is the callback in the Rejected state.
The then method returns a new Promise instance (note, not the original Promise instance). So you can write it chained, where a then method is followed by another THEN method.
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
Copy the code
The code above specifies two callback functions in turn, using the THEN method. After the first callback completes, the second callback is passed the result as an argument.
Using chained THEN, you can specify a set of callback functions that are called in order. In this case, the previous callback may still return a Promise object (with asynchronous operations), and the latter callback will wait for the state of the Promise object to change before it is invoked.
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
Copy the code
In the code above, the callback specified by the first then method returns another Promise object. At this point, the callback specified by the second THEN method waits for the new Promise object state to change. Call the first callback if it becomes Resolved, and the second if the status changes to Rejected.
The above code can be written more succinctly if the arrow function is used.
getJSON("/post/1.json").then(
post= > getJSON(post.commentURL)
).then(
comments= > console.log("resolved: ", comments),
err= > console.log("rejected: ", err)
);
Copy the code
Chain call promise.then()
then
Method returns a Promise object that allows the method chain to create a Promise chain.
A good example of chained promises is the Fetch API, which can be used to Fetch resources and queue promises up for execution when the resources are fetched.
The Fetch API is a promise-based mechanism, and calling Fetch () is equivalent to using New Promise() to define promsie.
const status = response= > {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
}
return Promise.reject(new Error(response.statusText))
}
const json = response= > response.json()
fetch('/todos.json')
.then(status) // Notice that the 'status' function is actually called here and also returns promise,
.then(json) // The only difference here is that the 'json' function returns the promise of the 'data' passed in at resolution,
.then(data= > { // This is why 'data' is the first argument to the anonymous function here.
console.log('Request received JSON response successfully', data)
})
.catch(error= > {
console.log('Request failed', error)
})
Copy the code
In this example, fetch() is called to get a list of TODO projects from the todos.json file in the domain root and create a promise chain.
Running fetch() returns a response with a number of attributes referenced in the attributes:
status
Is the value of the HTTP status code.statusText
, status message, OK if the request is successful.
Response also has a JSON () method that returns a promise, which is resolved by passing in the content of the processed and converted response body.
So, given these premises, what happens is that the first promise in the chain is the function we defined, status(), which checks the status of the response and rejects the promise if it is not a successful response (between 200 and 299).
This action causes the Promise chain to skip all listed chained promises and jump directly to the catch() statement at the bottom (which records the text and error message of the failed request).
If successful, the defined JSON () function is called. Since the previous promise returned a Response object on success, it is used as input for the second promise.
In this example, the processed JSON data is returned, so the third Promise accepts JSON directly:
.then((data) = > {
console.log('Request received JSON response successfully', data)
})
Copy the code
Promise.prototype.catch()
The catch() method returns a Promise and handles the rejection. It behaves the same as calling promise.prototype. then(undefined, onRejected).
In fact, calling obj.catch(onRejected) internal calls obj.then(undefined, onRejected). We explicitly use obj.catch(onRejected) and internally call obj.then(undefined, onRejected).
The promise.prototype.catch () method is an alias for. Then (null, Rejection) or. Then (undefined, Rejection) that specifies the callback when an error occurs.
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// Handle getJSON and errors that occurred while the previous callback function was running
console.log('Error! ', error);
});
Copy the code
In the code above, the getJSON() method returns a Promise object, and if the state becomes resolved, the callback specified by the then() method will be called; If an asynchronous operation throws an error, the status changes to Rejected, and the callback specified by the catch() method is called to handle the error. In addition, callbacks specified by the then() method are also caught by the catch() method if an error is thrown during execution.
p.then((val) = > console.log('fulfilled:', val))
.catch((err) = > console.log('rejected', err));
/ / is equivalent to
p.then((val) = > console.log('fulfilled:', val))
.then(null.(err) = > console.log("rejected:", err));
Copy the code
◾ Here is an example.
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
Copy the code
In the code above, a promise throws an error that is caught by the callback specified by the catch() method. Notice that the above notation is equivalent to the following two.
/ / write one
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) { reject(e); }}); promise.catch(function(error) {
console.log(error);
});
/ / write two
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
Copy the code
By comparing the two, you can see that the reject() method works the same way as throwing an error.
◾ If the Promise state has changed to Resolved, throwing an error will not work.
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
Copy the code
In the preceding code, a Promise is not caught if it is raised after the resolve statement. Because once a Promise state changes, it stays that state forever.
Errors in ◾ Promise objects are “bubbling” in nature and are passed backwards until they are caught. That is, an error is always caught by the next catch statement.
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// Handle errors from the first three promises
});
Copy the code
In the code above, there are three Promise objects: one generated by getJSON() and two generated by then(). Any error thrown by either of them will be caught by the last catch().
◾ As a general rule, do not define a Reject callback (the second argument to then) in the THEN () method. Always use the catch method.
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
Copy the code
In the above code, the second method is better than the first because it catches errors in the execution of the previous then method and is closer to the synchronous method (try/catch). Therefore, it is recommended to always use the catch() method instead of the second argument to the then() method.
◾ Unlike a traditional try/catch block of code, the error thrown by a Promise object is not passed to the outer code, that is, there is no response, unless there is a callback that uses the catch() method to specify error handling.
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// The next line is an error because x is not declared
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() = > { console.log(123)},2000);
// Uncaught (in promise) ReferenceError: x is not defined
/ / 123
Copy the code
Run the above code in your browser and after two seconds, you will see that the console prints “123” normally and does not block the code because of an error in the someAsyncThing() method.
In the code above, the Promise object generated by someAsyncThing() has a syntax error. When the browser runs this line, it will print ReferenceError: x is not defined, but it will not exit the process or terminate the script execution. After 2 seconds, it will still print 123. This means that errors within a Promise do not affect the code outside the Promise, which is colloquially known as “Promise eats errors.”
Handling errors
In the example in the previous chapter, a catch was attached to the promise chain.
When anything in the promise chain fails and raises an error or rejects a promise, control passes to the nearest catch() statement in the chain.
new Promise((resolve, reject) = > {
throw new Error('wrong')
}).catch(err= > {
console.error(err)
})
/ / or
new Promise((resolve, reject) = > {
reject('wrong')
}).catch(err= > {
console.error(err)
})
Copy the code
Cascading error
If an error is raised within a catch(), a second catch() can be attached to handle it, and so on.
new Promise((resolve, reject) = > {
throw new Error('wrong')
})
.catch(err= > {
throw new Error('wrong')
})
.catch(err= > {
console.error(err)
})
Copy the code
Promise.prototype.finally()
The finally() method is used to specify actions that will be performed regardless of the final state of the Promise object. This method was introduced as a standard in ES2018.
The finally() method returns a Promise. At the end of the promise, the specified callback function will be executed, whether the result is fulfilled or Rejected. This provides a way for code to be executed after a Promise is successfully completed or not.
This avoids the need to write the same statement once in both then() and catch().
promise
.then(result= >{...}). The catch (error= >{...}). Finally,() = > {···});
Copy the code
In the code above, regardless of the last state of the promise, the callback specified by the finally method is executed after the callback specified by then or catch.
The finally() method can be useful if you want to do some processing or cleanup after a promise has been executed regardless of the outcome.
◾ Because there is no way to know the final state of the promise, no arguments are taken in the finally callback function, which is only used when it will be executed regardless of the final result.
Resolve (2). Then (() => {}, () => {}) (resolved) Promise.resolve(2).finally(() => {}) resolved
◾ again, Promise. Reject (3). Then (() = > {}, () = > {}) (undefined) to the result of the fulfilled, Promise.reject(3).finally(() => {}) Rejected = 3
Concurrent promise. All ()
You can use promise.all () to make multiple concurrent requests and then perform some action after all promises have been resolved.
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
Promise.all([getUserAccount(), getUserPermissions()])
.then(function (results) {
const acct = results[0];
const perm = results[1];
});
Copy the code
The ES2015 destruct assignment syntax can also be executed:
Promise.all([getUserAccount, getUserPermissions]).then(([res1, res2]) = > {
console.log('results', res1, res2)
})
Copy the code
Of course, not limited to using Axios, any promise can be used in this way, such as:
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) = > {
setTimeout(resolve, 100.'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) = > {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
Copy the code
Racing promise. Race ()
The use of race is that multiple promise instances are passed in, and whoever runs faster executes a callback
First place only
The list of promises passed to race(), promise.race () starts running as soon as one promise is resolved, and an additional callback (passing in the result of the first resolved promise) runs only once.
Example:
const first = new Promise((resolve, reject) = > {
setTimeout(resolve, 500.'The first')})const second = new Promise((resolve, reject) = > {
setTimeout(resolve, 100.'Second')})Promise.race([first, second]).then(result= > {
console.log(result) / / the second
})
Copy the code
◾ Application Scenario
- 1. Put the asynchronous operation and timer together. If the timer is triggered first, it is regarded as timeout and informed to the user.
- 2. If images and other resources have multiple storage paths, but you are not sure which path is faster, you can use this method to request multiple paths at the same time, which path is the first to get the resources, which resources to use
- 3. If no result is obtained within the specified time, change the state of the Promise to
reject
, otherwise becomesresolve
Example:
◾ Put the asynchronous operation and timer together. If the timer is triggered first, it considers timeout and notifies the user:
function timeOut(time) {
let result = new Promise((resolve,reject) = > {
setTimeout(() = > {
resolve("Request timed out")
}, time) // To verify the method, set the time to a smaller value
});
return result;
}
Promise.race([timeOut(200), fetch('https://api.github.com/users/ruanyf')]).then(res= > {
console.log(res);
})
Copy the code
Fetch is natively supported by modern browsers, so we can run the above code directly on the browser:
To demonstrate the effect, set the setTimeout time to a small amount, and you can see that the timer completes first and the race() method executes the callback with the result of the timer
Set setTimeout a little larger, and this time the interface requests completion first, so race() performs the callback as a result of the interface
◾ Here is an example that changes the state of a Promise to Reject if no results are available within a specified time, or resolve otherwise.
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() = > reject(new Error('request timeout')), 5000)})]); p .then(console.log)
.catch(console.error);
Copy the code
In the code above, if the fetch method fails to return a result within 5 seconds, the status of the variable P changes to Rejected, which triggers the callback specified by the catch method.
Promise.allSettled()
The promise.allSettled () method returns a Promise after all the given promises have fulfilled or rejected, with an array of objects, each representing the corresponding Promise result.
This is often used when you have multiple asynchronous tasks that don’t depend on each other to complete successfully, or when you always want to know the outcome of each promise.
Promise.all(), by contrast, is better suited to relying on each other or ending immediately in either reject.
The promise.allSettled () method takes a set of Promise instances as parameters and wraps them into a new Promise instance. The wrapper instance will not complete until all of these parameter instances return the result, whether this is fulfilled or Rejected. This method was introduced by ES2020.
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3')];await Promise.allSettled(promises);
removeLoadingIndicator();// Remove the loaded scroll icon
Copy the code
The code above makes three requests to the server, and when all three are complete, whether the request succeeds or fails, the loaded scroll icon disappears.
This method returns a new Promise instance. Once it is fulfilled, the state is always fulfilled and will not become Rejected. After the state becomes depressing, the Promise listener receives an array of parameters, each member corresponding to an incoming promise.allSettled () Promise instance.
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
/ / /
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
Copy the code
In the code above, the return value of promises.allsettled (), allSettledPromise, may become a pity. Its listener receives an array results as an argument. Each member of this array is an object corresponding to two Promise instances passed in promise.allSettled (). Each object has a status attribute, whose value can only be the string fulfilled or the string Rejected. Someday, the object has the value attribute, and the object has the reason attribute, which corresponds to the return values of the two states.
◾ The following is an example of return value usage.
const promises = [ fetch('index.html'), fetch('https://does-not-exist/')];const results = await Promise.allSettled(promises);
// Filter out successful requests
const successfulPromises = results.filter(p= > p.status === 'fulfilled');
// Filter out failed requests and print the reason
const errors = results
.filter(p= > p.status === 'rejected')
.map(p= > p.reason);
Copy the code
Sometimes we don’t care about the results of asynchronous operations, only whether they end or not. In this case, the promise.allSettled () method is useful. Without this method, it would be a hassle to make sure everything was done. The promise.all () method doesn’t do this.
const urls = [ / *... * / ];
const requests = urls.map(x= > fetch(x));
try {
await Promise.all(requests);
console.log('All requests were successful. ');
} catch {
console.log('At least one request failed, and the others may not be finished. ');
}
Copy the code
In the code above, promise.all () cannot be sure that all requests are finished. Achieving this goal is cumbersome to write, and with Promise.AllSettled (), it’s easy.
Common Mistakes
◾ Uncaught TypeError: undefined is not a promise
If you receive an Uncaught TypeError: undefined is not a Promise error in the console, be sure to use new Promise() instead of Promise().
◾ UnhandledPromiseRejectionWarning
This means that the promise of the call is rejected, but there is no catch to handle the error. Adding a catch after a then is handled correctly.
reference
- Promise – JavaScript | MDN (mozilla.org)
- JavaScript Promise (nodejs.cn)
- Introduction to ECMAScript 6 (ES6) standards
- Read will, handwritten Promise principle, the most accessible version!!
- Use scenarios for promise. all and promise. race