- A Simple Guide to ES6 Promises
- By Brandon Morelli
- Translation from: The Gold Project
- This article is permalink: github.com/xitu/gold-m…
- Translator: Cooked fish
- Proofreader: Kezhenxu94 zhmhhu
The woods are lovely, dark and deep. But I have promises to keep, and miles to go before I sleep
Promise is one of the most exciting additions to JavaScript ES6. To support asynchronous programming, JavaScript uses callbacks, among other techniques. However, using callbacks can run into problems such as hell callbacks/doom pyramids. Promise is a pattern that greatly simplifies asynchronous programming by making the code look synchronous and avoiding problems with callbacks.
In this article, we’ll look at what promises are and how to use them to our advantage.
- Web Developer Roadmap 2018: An illustrated guide for front-end or back-end developers (links to courses are provided internally)
What is a Promise?
The ECMA committee defines a promise as —
A Promise is an object that is used as a placeholder for the final result of a delayed (and possibly asynchronous) computation.
Simply put, a promise is a container for future values. If you think about it, this is exactly how you would use the word “promise” in your normal, everyday conversation. Let’s say you book a ticket to India for a trip to the beautiful hill station of Darjeeling. After booking, you will get a ticket. The ticket is a promise from the airline, meaning you can get the appropriate seat on the day of departure. Essentially, a ticket is a placeholder for a future value, the seat.
Here’s another example — you promise your friends that you’ll return the book to them after you finish reading it. Here, your words serve as placeholders. It’s worth the book.
You can think of other examples of such promises in a variety of real-life situations, such as waiting at a doctor’s office, ordering food at a restaurant, handing out books at a library, and so on. All of these situations involve some form of promise. However, examples can only tell us so much, Talk is cheap, so let’s see the code.
Create a Promise
When the completion time of a task is uncertain or too long, we can create a promise. For example – depending on the connection speed, a network request can take as long as 10 ms or as long as 200 ms. We don’t want to wait for this data acquisition process. To you, 200 ms may seem like a small amount of time, but to a computer, that’s a very long time. Promise aims to make asynchrony simple and easy. Let’s look at the basics.
A new Promise is created using the Promise constructor. Like this –
const myPromise = new Promise((resolve, reject) = > {
if (Math.random() * 100< =90) {
resolve('Hello, Promises! ');
}
reject(new Error('In 10% of the cases, I fail. Miserably.'));
});
Copy the code
Promise the sample
If you look at this constructor, you can see that it takes a function with two arguments. This function is called an executor function, and it describes the calculation that needs to be done. The arguments to the executor function, often called resolve and reject, mark the final completion result of the successful and unsuccessful executor function, respectively.
Resolve and reject are functions in their own right, which are used to return a value to a PROMISE object. When the calculation is successful or the future value is ready, we use the resolve function to return the value. In this case, we say that the promise has been resolved.
If the calculation fails or an error is encountered, we tell the Promise object by passing the error object in the Reject function. At this point we say the promise has been rejected. Reject can accept values of any type. However, it is recommended to pass an Error object because it can help with debugging by looking at the stack trace.
In the example above, math.random () is used to generate a random number. There is a 90% chance that the promise will be successfully solved (assuming the probability is evenly distributed). The rest will be rejected.
The use of Promise
In the example above, we create a Promise and store it in myPromise. So how do we get the value passed through the resolve or reject functions? All promises have a.then() method. So the problem is solved, so let’s look at —
const myPromise = new Promise((resolve, reject) = > {
if (Math.random() * 100 < 90) {
console.log('resolving the promise ... ');
resolve('Hello, Promises! ');
}
reject(new Error('In 10% of the cases, I fail. Miserably.'));
});
// Two functions
const onResolved = (resolvedValue) = > console.log(resolvedValue);
const onRejected = (error) = > console.log(error);
myPromise.then(onResolved, onRejected);
// The same effect as above, the code is more concise
myPromise.then((resolvedValue) = > {
console.log(resolvedValue);
}, (error) = > {
console.log(error);
});
// Output the following statement with a 90% probability
// resolving the promise ...
// Hello, Promises!
// Hello, Promises!
Copy the code
The use of Promise
.then() receives two callback functions. The first callback is called when the Promise is resolved. The second callback is called when the PROMISE is rejected.
The two functions are defined in lines 10 and 11, onResolved and onRejected. They are passed as callbacks to.then () in line 13. You can also use the more common.then style on lines 16 through 20. It provides the same functionality as written above.
There are also some important things to note in the example above.
We create a Promise instance, myPromise. We append two.then handlers on lines 13 and 16, respectively. Even though they are functionally identical, they are considered different handlers. But –
- A promise can only be successfully or rejected once. It cannot succeed or fail twice, nor can it switch from success to failure, or vice versa.
- If you add a promise in a success/failure callback (i.e
.then
), the promise calls the callback correctly even if the event occurred before the callback was added.
This means that once a promise reaches its final state, even if you attach a.then handler several times, the state does not change (i.e., the computation does not restart).
To verify this, you can see a console.log statement in line 3. When you run the above code with the.then handler, the statement to be printed is printed only once. This indicates that the promise cached the results and will get the same results next time.
Another thing to note is that promises are characterized by early evaluation. Execution begins as soon as it is declared and bound to a variable. There is no.start or.begin method. As in the example above.
To ensure that promises are not started immediately but are evaluated lazily (evaluates lazily), we wrap them in functions. You’ll see an example later.
Capture the Promise
So far, we’ve only seen the case of Resolve conveniently. So what happens when the actuator function goes wrong? When an error occurs, the second callback to.then(), onRejected, is executed. Let’s look at an example
const myProimse = new Promise((resolve, reject) = > {
if (Math.random() * 100 < 90) {
reject(new Error('The promise was rejected by using reject function.'));
}
throw new Error('The promise was rejected by throwing an error');
});
myProimse.then(
() = > console.log('resolved'),
(error) = > console.log(error.message)
);
// Output the following statement with a 90% probability
// The promise was rejected by using reject function.
Copy the code
Promise error
This is the same as the first example, but now it executes the reject function 90% of the time and throws an error the remaining 10% of the time.
In lines 10 and 11, we define the onResolved and onRejected callbacks, respectively. Note that onRejected executes even if an error occurs. So there is no need to reject a promise by passing an error in the Reject function. In both cases, the promise will be rejected.
Since error handling is a requirement for robust programs, promises provide a shortcut to this situation. When we want to handle an error, we can use.catch(onRejected) to receive a callback: onRejected instead of using.then(null, () => {… }). The following code shows how to use the catch handler —
myProimse.catch(
(error) = > console.log(error.message)
);
Copy the code
Remember.catch is just a grammatical sugar for.then(undefined, onRejected).
Promise chain calls
The.then() and.catch() methods always return a promise. So you can link multiple.then together. Let’s understand it with an example.
First, we create a delay function that returns a promise. The returned promise is resolved after a given number of seconds. This is the implementation of it
const delay = (ms) = > new Promise(
(resolve) = > setTimeout(resolve, ms)
);
Copy the code
In this example, we use a function to wrap our promise so that it does not execute immediately. The delay function takes a time in milliseconds as an argument. Due to the nature of closures, the actuator function has access to ms parameters. It also contains a setTimeout function that calls the resolve function after ms milliseconds to effectively resolve the promise. This is an example usage
delay(5000).then(() = > console.log('Resolved after 5 seconds'));
Copy the code
The statement in the.then callback will run only after delay(5000) has resolved. When you run the code above, you will see Resolved After 5 seconds print out.
Here’s how we implement a chained call to.then() —
const delay = (ms) = > new Promise(
(resolve) = > setTimeout(resolve, ms)
);
delay(2000)
.then(() = > {
console.log('Resolved after 2 seconds')
return delay(1500);
})
.then(() = > {
console.log('Resolved after 1.5 seconds');
return delay(3000);
}).then(() = > {
console.log('Resolved after 3 seconds');
throw new Error(a); }).catch(() = > {
console.log('Caught an error.');
}).then(() = > {
console.log('Done.');
});
// Resolved after 2 seconds
// Resolved after 1.5 seconds
// Resolved after 3 seconds
// Caught an error.
// Done.
Copy the code
Promise chain calls
Let’s start at line 5. The steps taken are as follows
delay(2000)
The function returns a promise that can be resolved after two seconds.- The first one
.then()
The execution. It outputs a sentenceResolved after 2 seconds
. Then, it passes the calldelay(1500)
Return another promise. If a.then()
A promise is returned, and the ** solution (technically called settlement) ** of the promise is forwarded to the next.then
To call. - The chained call continues to the end.
Note also line 15. We threw an error inside the.then. That means the current promise is rejected and caught by the next.catch handler. Therefore, the sentence Caught an error is printed. However, a.catch itself always resolves to a promise and is not rejected (unless you intentionally throw an error). This is why.catch after.then is executed.
It is recommended to use.catch instead of.then with onResolved and onRejected. Here’s an example of why it’s best to do this —
const promiseThatResolves = () = > new Promise((resolve, reject) = > {
resolve();
});
// The rejected promise was not processed
promiseThatResolves().then(
() = > { throw new Error },
(err) = > console.log(err),
);
// Proper error handling
promiseThatResolves()
.then(() = > {
throw new Error(a); }) .catch(err= > console.log(err));
Copy the code
Line 1 creates a promise that is always resolvable. When you have a.then method with two callbacks, onResolved and onRejected, you can only handle errors and rejections from the executor functions. Suppose the handler in.then also throws an error. It does not cause the onRejected callback to be executed, as shown in lines 6-9.
But if you call.catch after.then, then.catch catches errors in both the executor function and the.then handler. This makes sense, since.then always returns a promise. As shown in lines 12-16.
You can execute all the code examples and learn more through practical applications. A good way to learn this is to reimplement a Promise through callbacks. If you use Node, many functions in FS and other modules are callbacks. There are utilities in Node that automatically convert callbacks to promises, such as Util.Promisify and Pify. However, if you are still in the learning phase, consider following WET (Write Everything Twice) and re-implementing or reading as many libraries/functions as you can. If not during the learning phase, especially in a production environment, use the DRY (Don’t Repeat Yourself) principle to motivate Yourself at regular intervals.
All, promise.race, and other static methods, and how to handle errors in promises. There are also some common anti-patterns and details that you should be aware of when creating a promise. You can refer to the following articles to get a better understanding of these topics.
If you would like me to cover these topics in another post, please respond to this post! 🙂
reference
- The ECMA Promise specification by Jake Archibald, Mozilla documentation, Google’s Promise Developer’s Guide, and the explore JavaScript Promise chapter and introduction to Promises.
I hope you enjoy this guest post! This article was written by Arfat Salmon specifically for CodeBurst.io
conclusion
Thanks for reading! If you finally decide to go down the road of web development, check out the 2018 Web Developer Roadmap.
If you’re striving to become a better JavaScript developer, check out: Improve your JavaScript interview — Learn Algorithms + Data Structures.
If you’d like to be a part of my weekly email list, consider entering your email here, or following me on Twitter.
If you find any errors in the translation or other areas that need improvement, you are welcome to revise and PR the translation in the Gold Translation program, and you can also get corresponding bonus points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
Diggings translation project is a community for translating quality Internet technical articles from diggings English sharing articles. The content covers the fields of Android, iOS, front end, back end, blockchain, products, design, artificial intelligence and so on. For more high-quality translations, please keep paying attention to The Translation Project, official weibo and zhihu column.