Promise

The meaning of Promise

Promise is a solution for asynchronous programming, which is more reasonable and more powerful than traditional solutions—callback functions and events. It was first proposed and implemented by the community, ES6 wrote it into the language standard, unified usage, and natively provided the Promise object.

The so-called Promise is simply a container that holds the result of an event (usually an asynchronous operation) that will end in the future. Syntactically speaking, a Promise is an object, from which you can obtain messages for asynchronous operations. Promise provides a unified API, and various asynchronous operations can be processed in the same way.

The Promise object has the following two characteristics.

(1) The state of the object is not affected by outsiders. The Promise object represents an asynchronous operation and has three states: pending (in progress), fulfilled (successful) and rejected (failed). Only the result of an asynchronous operation can determine which state is currently in, and no other operation can change this state. This is also the origin of the name Promise, which means "promise" in English, which means that it cannot be changed by other means.

(2) Once the state changes, it will not change again, and this result can be obtained at any time. There are only two possibilities for the state of the Promise object to change: from pending to fulfilled and from pending to rejected. As long as these two situations occur, the state will be frozen and will not change anymore. This result will always be maintained. At this time, it is called resolved (finalized). If the change has already occurred, and you add a callback function to the Promise object, you will get this result immediately. This is completely different from an event. The feature of an event is that if you miss it, you can listen to it and you will get no results.

Note that for the sake of writing convenience, the resolved at the end of this chapter refers to the fulfilled state, and does not include the rejected state.

With the Promise object, asynchronous operations can be expressed in a synchronous operation process, avoiding nested callback functions. In addition, the Promise object provides a unified interface, making it easier to control asynchronous operations.

Promise also has some disadvantages. First of all, Promise cannot be cancelled. Once it is created, it will be executed immediately and cannot be cancelled halfway. Secondly, if the callback function is not set, the error thrown inside the Promise will not be reflected to the outside. Third, when in the pending state, it is impossible to know which stage it is currently in (just started or is about to be completed).

If certain events happen repeatedly, generally speaking, using Stream mode is a better choice than deploying Promise.

Basic usage

ES6 stipulates that the Promise object is a constructor function used to generate a Promise instance.

The following code creates an instance of Promise.

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* Asynchronous operation succeeded*/){
    resolve(value);
  } else {
    reject(error);
  }
});

The Promise constructor accepts a function as a parameter, and the two parameters of the function are resolve and reject. They are two functions, provided by the JavaScript engine, and do not need to be deployed by yourself.

The role of the resolve function is to change the state of the Promise object from "unfinished" to "success" (that is, from pending to resolved), which is called when the asynchronous operation succeeds, and the result of the asynchronous operation is used as a parameter Pass it out; the role of the reject function is to change the state of the Promise object from "unfinished" to "failed" (that is, from pending to rejected), call it when the asynchronous operation fails, and report the asynchronous operation The error is passed as a parameter.

After the Promise instance is generated, you can use the then method to specify the callback functions of the resolved state and the rejected state respectively.

promise.then(
  function (value) {
    // success
  },
  function (error) {
    // failure
  }
);

The then method can accept two callback functions as parameters. The first callback function is called when the state of the Promise object becomes resolved, and the second callback function is called when the state of the Promise object becomes rejected. Both of these functions are optional and do not have to be provided. They all accept the value passed by the Promise object as a parameter.

Below is a simple example of a Promise object.

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, "done");
  });
}

timeout(100).then((value) => {
  console.log(value);
});

In the above code, the timeout method returns a Promise instance, which represents a result that will occur after a period of time. After the specified time (ms parameter), the state of the Promise instance becomes resolved, and the callback function bound by the then method will be triggered.

Once the Promise is created, it will be executed immediately.

let promise = new Promise(function (resolve, reject) {
  console.log("Promise");
  resolve();
});

promise.then(function () {
  console.log("resolved.");
});

console.log("Hi!");

// Promise
// Hi!
// resolved

In the above code, the Promise is executed immediately after it is created, so the first output is Promise. Then, the callback function specified by the then method will be executed after all the synchronization tasks of the current script are executed, so resolved is output at the end.

The following is an example of asynchronous loading of pictures.

function loadImageAsync(url) {
  return new Promise(function (resolve, reject) {
    const image = new Image();

    image.onload = function () {
      resolve(image);
    };

    image.onerror = function () {
      reject(new Error("Could not load image at " + url));
    };

    image.src = url;
  });
}

In the above code, an asynchronous operation of image loading is wrapped with Promise. If the loading is successful, call the resolve method, otherwise call the reject method.

The following is an example of an Ajax operation implemented with a Promise object.

const getJSON = function (url) {
  const promise = new Promise(function (resolve, reject) {
    const handler = function () {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
  });

  return promise;
};

getJSON("/posts.json").then(
  function (json) {
    console.log("Contents: " + json);
  },
  function (error) {
    console.error("Something went wrong", error);
  }
);

In the above code, getJSON is an encapsulation of the XMLHttpRequest object, which is used to issue an HTTP request for JSON data and return a Promise object. It should be noted that inside getJSON, both the resolve function and the reject function are called with parameters.

If the resolve function and the reject function are called with parameters, their parameters will be passed to the callback function. The parameter of the reject function is usually an instance of the Error object, which represents the error thrown; besides the normal value, the parameter of the resolve function may also be another Promise instance, such as the following.

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
});

In the above code, p1 and p2 are both Promise instances, but the resolve method of p2 takes p1 as a parameter, that is, the result of an asynchronous operation is to return another asynchronous operation.

Note that the state of p1 will be passed to p2 at this time, that is, the state of p1 determines the state of p2. If the status of p1 is pending, then the callback function of p2 will wait for the status of p1 to change; if the status of p1 is already resolved or rejected, then the callback of p2 The function will be executed immediately.

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error("fail")), 3000);
});

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000);
});

p2.then((result) => console.log(result)).catch((error) => console.log(error));
// Error: fail

In the above code, p1 is a Promise, which becomes rejected after 3 seconds. The status of p2 changes after 1 second, and the resolve method returns p1. Since p2 returns another Promise, the state of p2 itself is invalid. The state of p2 is determined by the state of p1. Therefore, the following then statements all become for the latter (p1). After another 2 seconds, p1 becomes rejected, causing the callback function specified by the catch method to be triggered.

Note that calling resolve or reject will not terminate the execution of the Promise parameter function.

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then((r) => {
  console.log(r);
});
// 2
// 1

In the above code, after calling resolve(1), the following console.log(2) will still be executed and will be printed out first. This is because the immediately resolved Promise is executed at the end of the current round of event loop, which is always later than the synchronous task of the current round of loop.

Generally speaking, after calling resolve or reject, the mission of Promise is completed. Subsequent operations should be placed in the then method, and should not be written directly after resolve or reject. Therefore, it is best to add a return statement in front of them so that there are no surprises.

new Promise((resolve, reject) => {
  return resolve(1);
  // The following statement will not be executed
  console.log(2);
});

Promise.prototype.then()

Promise instances have the then method, that is, the then method is defined on the prototype object Promise.prototype. Its role is to add a callback function when the state of the Promise instance changes. As mentioned earlier, the first parameter of the then method is the callback function of the resolved state, and the second parameter is the callback function of the rejected state. They are all optional.

The then method returns a new Promise instance (note, not the original Promise instance). Therefore, chain writing can be used, that is, another then method is called after the then method.

getJSON("/posts.json")
  .then(function (json) {
    return json.post;
  })
  .then(function (post) {
    // ...
  });

The above code uses the then method to specify two callback functions in turn. After the first callback function is completed, the return result will be used as a parameter and passed to the second callback function.

Using chained then, you can specify a set of callback functions to be called in order. At this time, the previous callback function may return a Promise object (that is, asynchronous operation). At this time, the latter callback function will wait for the state of the Promise object to change before being called.

getJSON("/post/1.json")
  .then(function (post) {
    return getJSON(post.commentURL);
  })
  .then(
    function (comments) {
      console.log("resolved: ", comments);
    },
    function (err) {
      console.log("rejected: ", err);
    }
  );

In the above code, the callback function specified by the first then method returns another Promise object. At this time, the callback function specified by the second then method will wait for the state of this new Promise object to change. If it becomes resolved, the first callback function is called, and if the status becomes rejected, the second callback function is called.

If an arrow function is used, the above code can be written more concisely.

getJSON("/post/1.json")
  .then((post) => getJSON(post.commentURL))
  .then(
    (comments) => console.log("resolved: ", comments),
    (err) => console.log("rejected: ", err)
  );

Promise.prototype.catch()

The Promise.prototype.catch() method is an alias of .then(null, rejection) or .then(undefined, rejection), which is used to specify the callback function when an error occurs.

getJSON("/posts.json")
  .then(function (posts) {
    // ...
  })
  .catch(function (error) {
    // Handle errors that occur when getJSON and the previous callback function are running
    console.log("An error occurred!", error);
  });

In the above code, the getJSON() method returns a Promise object. If the state of the object becomes resolved, the callback function specified by the then() method will be called; if the asynchronous operation throws an error, the state will change For rejected, the callback function specified by the catch() method will be called to handle this error. In addition, if the callback function specified by the then() method throws an error during operation, it will also be caught by the catch() method.

p.then((val) => console.log("fulfilled:", val)).catch((err) =>
  console.log("rejected", err)
);

// Equivalent to
p.then((val) => console.log("fulfilled:", val)).then(null, (err) =>
  console.log("rejected:", err)
);

Below is an example.

const promise = new Promise(function (resolve, reject) {
  throw new Error("test");
});
promise.catch(function (error) {
  console.log(error);
});
// Error: test

In the above code, an error thrown by promise is caught by the callback function specified by the catch() method. Note that the above writing is equivalent to the following two writings.

// Writing method one
const promise = new Promise(function (resolve, reject) {
  try {
    throw new Error("test");
  } catch (e) {
    reject(e);
  }
});
promise.catch(function (error) {
  console.log(error);
});

// Writing method two
const promise = new Promise(function (resolve, reject) {
  reject(new Error("test"));
});
promise.catch(function (error) {
  console.log(error);
});

Comparing the above two writing methods, we can find that the function of reject() method is equivalent to throwing an error.

If the Promise status has become resolved, throwing an error again is invalid.

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

In the above code, if the Promise throws an error after the resolve statement, it will not be caught, which means it is not thrown. Because once the state of the Promise changes, it will remain in that state forever and won't change anymore.

The error of the Promise object has a "bubble" nature and will be passed backwards until it is caught. In other words, errors will always be 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 the errors generated by the first three Promise
  });

In the above code, there are three Promise objects: one is generated by getJSON(), and two are generated by then(). Any errors thrown by them will be caught by the last catch().

Generally speaking, do not define the callback function of the Reject state in the then() method (that is, the second parameter of then), 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
  });

In the above code, the second way of writing is better than the first way of writing. The reason is that the second way of writing can catch errors in the execution of the previous then method, and it is also closer to the synchronous way of writing (try/catch). Therefore, it is recommended to always use the catch() method instead of the second parameter of the then() method.

Unlike the traditional try/catch code block, if the error handling callback function is not specified using the catch() method, the error thrown by the Promise object will not be passed to the outer code, that is, there will be no response. .

const someAsyncThing = function () {
  return new Promise(function (resolve, reject) {
    // The next line will report 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

In the above code, the Promise object generated by the someAsyncThing() function has a syntax error inside. When the browser runs to this line, it will print out the error message ReferenceError: x is not defined, but it will not exit the process or terminate the script execution. After 2 seconds, it will still output 123. This means that errors inside the Promise will not affect the code outside the Promise. The popular saying is "Promise will eat the error".

This script is executed on the server, and the exit code is 0 (that means the execution is successful). However, Node.js has an unhandledRejection event, which specifically listens for uncaught reject errors. The above script will trigger the listener function of this event, and errors can be thrown in the listener function.

process.on("unhandledRejection", function (err, p) {
  throw err;
});

In the above code, the listener function of the unhandledRejection event has two parameters. The first is the error object, and the second is the Promise instance that reported the error. It can be used to understand the environment in which the error occurred.

Note that Node has plans to abolish the unhandledRejection event in the future. If there is an uncaught error inside the Promise, the process will be terminated directly, and the exit code of the process is not 0.

Look at the following example again.

const promise = new Promise(function (resolve, reject) {
  resolve("ok");
  setTimeout(function () {
    throw new Error("test");
  }, 0);
});
promise.then(function (value) {
  console.log(value);
});
// ok
// Uncaught Error: test

In the above code, Promise specifies that an error will be thrown in the next round of the "event loop". At that time, the operation of the Promise has ended, so this error is thrown outside the Promise function, will bubble up to the outermost layer, and become an uncaught error.

It is generally recommended that the Promise object should be followed by the catch() method, so that errors that occur inside the Promise can be handled. The catch() method returns a Promise object, so you can call the then() method later.

const someAsyncThing = function () {
  return new Promise(function (resolve, reject) {
    // The next line will report an error because x is not declared
    resolve(x + 2);
  });
};

someAsyncThing()
  .catch(function (error) {
    console.log("oh no", error);
  })
  .then(function () {
    console.log("carry on");
  });
// oh no [ReferenceError: x is not defined]
// carry on

After the above code runs the callback function specified by the catch() method, it will then run the callback function specified by the later then() method. If no error is reported, the catch() method will be skipped.

Promise.resolve()
  .catch(function (error) {
    console.log("oh no", error);
  })
  .then(function () {
    console.log("carry on");
  });
// carry on

Because there is no error in the above code, the catch() method is skipped and the subsequent then() method is executed directly. At this point, if an error is reported in the then() method, it has nothing to do with the previous catch().

In the catch() method, errors can be thrown again.

const someAsyncThing = function () {
  return new Promise(function (resolve, reject) {
    // The next line will report an error because x is not declared
    resolve(x + 2);
  });
};

someAsyncThing()
  .then(function () {
    return someOtherAsyncThing();
  })
  .catch(function (error) {
    console.log("oh no", error);
    // The next line will report an error because y is not declared
    y + 2;
  })
  .then(function () {
    console.log("carry on");
  });
// oh no [ReferenceError: x is not defined]

In the above code, the catch() method throws an error, because there is no other catch() method afterwards, so this error will not be caught and will not be passed to the outer layer. If you rewrite it, the result will be different.

someAsyncThing()
  .then(function () {
    return someOtherAsyncThing();
  })
  .catch(function (error) {
    console.log("oh no", error);
    // The next line will report an error because y is not declared
    y + 2;
  })
  .catch(function (error) {
    console.log("carry on", error);
  });
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

In the above code, the second catch() method is used to catch the error thrown by the previous catch() method.

Promise.prototype.finally()

The finally() method is used to specify the operation that will be performed regardless of the final state of the Promise object. This method is introduced into the standard by ES2018.

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

In the above code, regardless of the final state of promise, after the callback function specified by then or catch is executed, the callback function specified by the finally method will be executed.

The following is an example, the server uses Promise to process the request, and then uses the finally method to shut down the server.

server
  .listen(port)
  .then(function () {
    // ...
  })
  .finally(server.stop);

The callback function of the finally method does not accept any parameters, which means that there is no way to know whether the previous Promise status is fulfilled or rejected. This shows that the operations in the finally method should be state-independent and not dependent on the execution result of the Promise.

finally is essentially a special case of the then method.

promise.finally(() => {
  // statement
});

// Equivalent to
promise.then(
  (result) => {
    // statement
    return result;
  },
  (error) => {
    // statement
    throw error;
  }
);

In the above code, if the finally method is not used, the same statement needs to be written once for both success and failure. With the finally method, you only need to write it once.

Its implementation is also very simple.

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    (value) => P.resolve(callback()).then(() => value),
    (reason) =>
      P.resolve(callback()).then(() => {
        throw reason;
      })
  );
};

In the above code, regardless of whether the previous Promise is fulfilled or rejected, the callback function callback will be executed.

You can also see from the above implementation that the finally method always returns the original value.

// The value of resolve is undefined
Promise.resolve(2).then(
  () => {},
  () => {}
);

// The value of resolve is 2
Promise.resolve(2).finally(() => {});

// The value of reject is undefined
Promise.reject(3).then(
  () => {},
  () => {}
);

// The value of reject is 3
Promise.reject(3).finally(() => {});

Promise.all()

The Promise.all() method is used to wrap multiple Promise instances into a new Promise instance.

const p = Promise.all([p1, p2, p3]);

In the above code, the Promise.all() method accepts an array as a parameter. p1, p2, and p3 are all Promise instances. If they are not, the following Promise.resolve will be called first Method, the parameters are converted to Promise instances, and then processed further. In addition, the parameter of the Promise.all() method may not be an array, but it must have an Iterator interface, and each member returned is a Promise instance.

The state of p is determined by p1, p2, and p3, which are divided into two cases.

(1) Only when the states of p1, p2, and p3 all become fulfilled, the state of p will become fulfilled, at this time p1, p2, p3 The return value of is formed into an array and passed to the callback function of p.

(2) As long as one of p1, p2, and p3 is rejected, the status of p becomes rejected, and the return value of the first instance of reject , Will be passed to the callback function of p.

The following is a specific example.

// Generate an array of Promise objects
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON("/post/" + id + ".json");
});

Promise.all(promises)
  .then(function (posts) {
    // ...
  })
  .catch(function (reason) {
    // ...
  });

In the above code, promises is an array containing 6 Promise instances. Only when the status of these 6 instances becomes fulfilled, or one of them becomes rejected, will the method be called after the Promise.all Callback function.

Here is another example.

const databasePromise = connectDatabase();

const booksPromise = databasePromise.then(findAllBooks);

const userPromise = databasePromise.then(getCurrentUser);

Promise.all([booksPromise, userPromise]).then(([books, user]) =>
  pickTopRecommendations(books, user)
);

In the above code, booksPromise and userPromise are two asynchronous operations. The callback function pickTopRecommendations will be triggered only when their results are returned.

Note that if the Promise instance as a parameter defines the catch method by itself, once it is rejected, it will not trigger the catch method of Promise.all().

const p1 = new Promise((resolve, reject) => {
  resolve("hello");
})
  .then((result) => result)
  .catch((e) => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error("Reported an error");
})
  .then((result) => result)
  .catch((e) => e);

Promise.all([p1, p2])
  .then((result) => console.log(result))
  .catch((e) => console.log(e));
// ["hello", Error: reported an error]

In the above code, p1 will be resolved, p2 will be rejected first, but p2 has its own catch method, which returns a new Promise instance, and the actual point pointed to by p2 The above is an example of this. This instance will also become resolved after executing the catch method, causing both instances in the Promise.all() method parameter to be resolved, so the callback function specified by the then method will be called. The callback function specified by the catch method will not be called.

If p2 does not have its own catch method, it will call the catch method of Promise.all().

const p1 = new Promise((resolve, reject) => {
  resolve("hello");
}).then((result) => result);

const p2 = new Promise((resolve, reject) => {
  throw new Error("Reported an error");
}).then((result) => result);

Promise.all([p1, p2])
  .then((result) => console.log(result))
  .catch((e) => console.log(e));
// Error: reported an error

Promise.race()

The Promise.race() method also wraps multiple Promise instances into a new Promise instance.

const p = Promise.race([p1, p2, p3]);

In the above code, as long as one instance of p1, p2, and p3 changes state first, the state of p will change accordingly. The return value of the Promise instance that changes first is passed to the callback function of p.

The parameters of the Promise.race() method are the same as the Promise.all() method. If it is not a Promise instance, the Promise.resolve() method mentioned below will be called first to convert the parameters to a Promise instance. Further processing.

The following is an example. If no result is obtained within the specified time, the state of the Promise is changed to reject, otherwise it is changed to resolve.

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);

In the above code, if the fetch method fails to return a result within 5 seconds, the state of the variable p will become rejected, which triggers the callback function specified by the catch method.

Promise.allSettled()

The Promise.allSettled() method accepts a set of Promise instances as parameters and wraps them into a new Promise instance. Only when all these parameter instances return results, whether it is fulfilled or rejected, the packaging instance will end. This method was introduced by ES2020.

const promises = [fetch("/api-1"), fetch("/api-2"), fetch("/api-3")];

await Promise.allSettled(promises);
removeLoadingIndicator();

The above code sends three requests to the server and waits until all three requests are over. Regardless of whether the request succeeds or fails, the loaded scroll icon will disappear.

Once the new Promise instance returned by this method ends, the status will always be fulfilled and will not become rejected. After the status becomes fulfilled, the parameters received by the Promise listener function are an array, and each member corresponds to a Promise instance passed in to Promise.allSettled().

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}
//]

In the above code, the return value of Promise.allSettled() is allSettledPromise, and the status can only become fulfilled. The parameter received by its listening function is the array results. Each member of the array is an object, corresponding to the two Promise instances passed in to Promise.allSettled(). Every object has a status property, and the value of this property can only be the string fulfilled or the string rejected. When fulfilled, the object has a value property, and when it is rejected, it has a reason property, which corresponds to the return value 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 output the reason
const errors = results
  .filter((p) => p.status === "rejected")
  .map((p) => p.reason);

Sometimes, we don't care about the results of asynchronous operations, only whether these operations are over. At this time, the Promise.allSettled() method is very useful. Without this method, it would be troublesome to ensure that all operations are completed. The Promise.all() method cannot do this.

const urls = [
  /* ... */
];
const requests = urls.map((x) => fetch(x));

try {
  await Promise.all(requests);
  console.log("All requests are successful.");
} catch {
  console.log(
    "At least one request failed, other requests may not be over yet."
  );
}

In the above code, Promise.all() cannot be sure that all requests are completed. To achieve this goal, it is very troublesome to write. With Promise.allSettled(), it is very easy.

Promise.any()

ES2021 introduced the Promise.any() method. This method accepts a set of Promise instances as parameters and wraps them into a new Promise instance to return. As long as one of the parameter instances becomes the fulfilled state, the packaging instance will become the fulfilled state; if all the parameter instances become the rejected state, the packaging instance will become the rejected state.

The Promise.any() method is similar to the Promise.race() method, except that it does not end because a certain Promise becomes the rejected state.

const promises = [
  fetch("/endpoint-a").then(() => "a"),
  fetch("/endpoint-b").then(() => "b"),
  fetch("/endpoint-c").then(() => "c"),
];
try {
  const first = await Promise.any(promises);
  console.log(first);
} catch (error) {
  console.log(error);
}

In the above code, the parameter array of the Promise.any() method contains three Promise operations. As long as one of them becomes fulfilled, the Promise object returned by Promise.any() becomes fulfilled. If all three operations become rejected, then the await command will throw an error.

The error thrown by Promise.any() is not a general error, but an instance of AggregateError. It is equivalent to an array, and each member corresponds to an error thrown by the operation of rejected. The following is an implementation example of AggregateError.

new AggregateError() extends Array -> AggregateError

const err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
throw err;

When catching errors, if you don't need the try...catch structure and await command, you can write like the following.

Promise.any(promises).then(
  (first) => {
    // Any of the promises was fulfilled.
  },
  (error) => {
    // All of the promises were rejected.
  }
);

Below is an example.

var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result); // 42
});

Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results); // [-1, Infinity]
});

Promise.resolve()

Sometimes it is necessary to convert an existing object into a Promise object, and the Promise.resolve() method plays this role.

const jsPromise = Promise.resolve($.ajax("/whatever.json"));

The above code converts the deferred object generated by jQuery into a new Promise object.

Promise.resolve() is equivalent to the following writing.

Promise.resolve("foo");
// Equivalent to
new Promise((resolve) => resolve("foo"));

The parameters of the Promise.resolve() method are divided into four cases.

(1) The parameter is a Promise instance

If the parameter is a Promise instance, then Promise.resolve will return this instance without any modification.

(2) The parameter is a thenable object

The thenable object refers to the object with the then method, such as the following object.

let thenable = {
  then: function (resolve, reject) {
    resolve(42);
  },
};

The Promise.resolve() method will convert this object into a Promise object, and then immediately execute the then() method of the thenable object.

let thenable = {
  then: function (resolve, reject) {
    resolve(42);
  },
};

let p1 = Promise.resolve(thenable);
p1.then(function (value) {
  console.log(value); // 42
});

In the above code, after the then() method of the thenable object is executed, the state of the object p1 becomes resolved, which immediately executes the callback function specified by the last then() method, and outputs 42.

(3) The parameter is not an object with the then() method, or is not an object at all

If the parameter is a primitive value, or an object that does not have a then() method, the Promise.resolve() method returns a new Promise object with a status of resolved.

const p = Promise.resolve("Hello");

p.then(function (s) {
  console.log(s);
});
// Hello

The above code generates a new instance of Promise object p. Since the string Hello is not an asynchronous operation (the judgment method is that the string object does not have the then method), the state of the returned Promise instance is resolved from the moment it is generated, so the callback function will be executed immediately. The parameters of the Promise.resolve() method will be passed to the callback function at the same time.

(4) Without any parameters

The Promise.resolve() method allows to call without parameters and directly return a Promise object in the state of resolved.

Therefore, if you want to get a Promise object, the more convenient way is to directly call the Promise.resolve() method.

const p = Promise.resolve();

p.then(function () {
  // ...
});

The variable p in the above code is a Promise object.

It should be noted that the Promise object of resolve() immediately is executed at the end of the current “event loop”, not at the beginning of the next “event loop”.

setTimeout(function () {
  console.log("three");
}, 0);

Promise.resolve().then(function () {
  console.log("two");
});

console.log("one");

// one
// two
// three

In the above code, setTimeout(fn, 0) is executed at the beginning of the next round of "event loop", and Promise.resolve() is executed at the end of this round of "event loop", console.log('one') Is executed immediately, so the output is the first.

Promise.reject()

The Promise.reject(reason) method will also return a new Promise instance with a status of rejected.

const p = Promise.reject("Something went wrong");
// Equivalent to
const p = new Promise((resolve, reject) => reject("Something went wrong"));

p.then(null, function (s) {
  console.log(s);
});
// error

The above code generates an instance p of a Promise object with a status of rejected, and the callback function will be executed immediately.

The parameters of the Promise.reject() method will be used as the reason for reject intact and become the parameters of the subsequent method.

Promise.reject("Something went wrong").catch((e) => {
  console.log(e === "Something went wrong");
});
// true

In the above code, the parameter of the Promise.reject() method is a string, and the parameter e of the subsequent catch() method is this string.

Application

Load picture

We can write the loading of the picture as a Promise, once the loading is completed, the state of the Promise will change.

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

Combination of Generator function and Promise

Use the Generator function to manage the process. When an asynchronous operation is encountered, it usually returns a Promise object.

function getFoo() {
  return new Promise(function (resolve, reject) {
    resolve("foo");
  });
}

const g = function* () {
  try {
    const foo = yield getFoo();
    console.log(foo);
  } catch (e) {
    console.log(e);
  }
};

function run(generator) {
  const it = generator();

  function go(result) {
    if (result.done) return result.value;

    return result.value.then(
      function (value) {
        return go(it.next(value));
      },
      function (error) {
        return go(it.throw(error));
      }
    );
  }

  go(it.next());
}

run(g);

In the Generator function g of the above code, there is an asynchronous operation getFoo, which returns a Promise object. The function run is used to process this Promise object and call the next next method.

Promise.try()

In actual development, we often encounter a situation: I don't know or don't want to distinguish whether the function f is a synchronous function or an asynchronous operation, but I want to use Promise to handle it. Because in this way, regardless of whether f contains asynchronous operations, the then method can be used to specify the next step, and the catch method can be used to handle errors thrown by f. Generally, the following wording will be used.

Promise.resolve().then(f);

The above writing has a disadvantage that if f is a synchronous function, then it will be executed at the end of this round of event loop.

const f = () => console.log("now");
Promise.resolve().then(f);
console.log("next");
// next
// now

In the above code, the function f is synchronous, but after it is wrapped with Promise, it becomes asynchronous execution.

So is there a way to make synchronous functions execute synchronously and asynchronous functions execute asynchronously, and let them have a unified API? The answer is yes, and there are two ways to write it. The first way of writing is to use the async function.

const f = () => console.log("now");
(async () => f())();
console.log("next");
// now
// next

In the above code, the second line is an anonymous function that is executed immediately, which will execute the async function immediately, so if f is synchronous, you will get a synchronous result; if f is asynchronous, then You can use then to specify the next step, as shown below.

(async () => f())()
.then(...)

It should be noted that async () => f() will eat the error thrown by f(). So, if you want to catch errors, use the promise.catch method.

(async () => f())()
.then(...)
.catch(...)

The second way of writing is to use new Promise().

const f = () => console.log("now");
(() => new Promise((resolve) => resolve(f())))();
console.log("next");
// now
// next

The above code also uses an anonymous function that is executed immediately to execute new Promise(). In this case, the synchronization function is also executed synchronously.

Since this is a very common requirement, there is now a proposal that provides the Promise.try method instead of the above.

const f = () => console.log("now");
Promise.try(f);
console.log("next");
// now
// next

In fact, Promise.try has existed for a long time, the Promise library Bluebird, [Q](https://github .com/kriskowal/q/wiki/API-Reference#promisefcallargs) and when, provided long ago This method is used.

Since Promise.try provides a unified processing mechanism for all operations, if you want to use the then method to manage the process, it is best to wrap it with Promise.try. This has many benefits, one of which is ok Better manage exceptions.

function getUsername(userId) {
  return database.users.get({ id: userId }).then(function (user) {
    return user.name;
  });
}

In the above code, database.users.get() returns a Promise object. If an asynchronous error is thrown, you can use the catch method to catch it, like the following.

database.users.get({id: userId})
.then(...)
.catch(...)

But database.users.get() may also throw synchronization errors (such as database connection errors, depending on the implementation method), then you have to use try...catch to catch.

try {
  database.users.get({id: userId})
  .then(...)
  .catch(...)
} catch (e) {
  // ...
}

The above writing method is very clumsy. At this time, you can use promise.catch() to catch all synchronous and asynchronous errors.

Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

In fact, Promise.try simulates a try code block, just like promise.catch simulates a catch code block.