Promises

Promises are another option in addition to callbacks, events for asynchronous programming in JavaScript. Before ES6, libraries such as bluebird (http://bluebirdjs.com) provided promises compatible with the Promises/A+ spec. 

A promise represents the eventual result of an asynchronous operation, as described in the Promises/A+ spec. The result would be a successful completion or a failure. And it provides methods such as .then(), and .catch() for chaining multiple asynchronous operations together that would make the code similar to synchronous code that is easy to follow.

The features of ES6 promises are a subset of those provided by libraries such as bluebird. In this book, the promises we use are those defined in the ES6 language spec unless otherwise specified.

Let's look at an example in which we will get a list of projects from the server and then get tasks of those projects from the server in a separate API call. And then we will render it. The implementation here is a simplified version for demonstrating the differences between using callbacks and promises. We use setTimeout to stimulate an asynchronous operation.

First of all, let's see the version that uses callbacks:

1.  function getProjects(callback) {
2. // Use setTimeout to stimulate calling server API
3. setTimeout(() => {
4. callback([{id:1, name:'Project A'},{id:2, name:'Project B'}]);
5. }, 100);
6. }
7. function getTasks(projects, callback) {
8. // Use setTimeout to stimulate calling server API
9. setTimeout(() => {
10. // Return tasks of specified projects
11. callback([{id: 1, projectId: 1, title: 'Task A'},
12. {id: 2, projectId: 2, title: 'Task B'}]);
13. }, 100);
14. }
15. function render({projects, tasks}) {
16. console.log(`Render ${projects.length} projects and
${tasks.length} tasks`);
17. }
18. getProjects((projects) => {
19. getTasks(projects, (tasks) => {
20. render({projects, tasks});
21. });
22. });

As you can see in lines 18 to 22, we use callbacks to organize asynchronous calls. And even though the code here is greatly simplified, you can still see that the getProjects(), getTasks(), and render() methods are nested, creating a pyramid of doom or callback hell.

Now, let's see the version that uses promises:

1.  function getProjects() {
2. return new Promise((resolve, reject) => {
3. setTimeout(() => {
4. resolve([{id:1, name:'Project A'},{id:2, name:'Project B'}]);
5. }, 100);
6. });
7. }
8. function getTasks(projects) {
9. return new Promise((resolve, reject) => {
10. setTimeout(() => {
11. resolve({projects,
12. tasks:['Buy three tickets', 'Book a hotel']});
13. }, 100);
14. });
15. }
16. function render({projects, tasks}) {
17. console.log(`Render ${projects.length} projects and ${tasks.length} tasks`);
18. }
19. getProjects()
20. .then(getTasks)
21. .then(render)
22. .catch((error) => {
23. // handle error
24. });

In lines 1 to 15, in the getProjects() and getTasks() method, we wrap up asynchronous operations inside a Promise object that is returned immediately. The Promise constructor function takes a function as its parameter. This function is called an executor function, which is executed immediately with two arguments, a resolve function and a reject function. These two functions are provided by the Promise implementation. When the asynchronous operation completes, you call the resolve function with the result of the operation or no result at all. And when the operation fails, you can use the reject function to reject the promise. Inside the executor function, if any error is thrown, the promise will be rejected too.

A promise is in one of these three states:

  • Pending: The initial state of a promise
  • Fulfilled: The state when the operation has completed successfully
  • Rejected: The state when the operation didn't complete successfully due to an error or any other reason

You cannot get the state of a promise programmatically. Instead, you can use the .then() method of the promise to take action when the state changes to fulfilled, and use the .catch() method to react when the state changed to rejected or an error are thrown during the operation.

The .then() method of a promise object takes two functions as its parameters. The first function in the argument is called when the promise is fulfilled. So it is usually referenced as onFulfilledand the second one is called when the promise is rejected, and it is usually referenced as onRejected. The .then() method will also return a promise object. As you can see in lines 19 to 21, we can use .then() to chain all the operations. The .catch() method in line 22 is actually a syntax sugar of .then(undefined, onRejected). Here, we put it as the last one in the chain to catch all the rejects and errors. You can also add .then() after .catch() to perform further operations.

The ES6 Promise also provides the .all(iterable) method to aggregate the results of multiple promises and the .race(iterable) method to return a promise that fulfills or rejects as soon as one of the promises in the iterable fulfills or rejects.

Another two methods that ES6 Promise provides are the .resolve(value) method and the .reject(reason) method. The .resolve(value) method returns a Promise object. When the value is a promise, the returned promise will adopt its eventual state. That is when you call the .then() method of the returned promise; the onFulfilled handler will get the result of the value promise. When the value is not a promise, the returned promise is in a fulfilled state and its result is a value. The .reject(reason) method returns a promise that is in a rejected state with the reason passed in to indicate why it is rejected.

As you might have noticed, promises do not help you write less code, but they do help you to improve your code's readability by providing a better way of organizing code flow.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.178.9