Create a Promise

Promise is a standard JavaScript built-in object. Used to store the execution results of an asynchronous task for future use.

Create a Promise object:

let promise = new Promise(function(resolve, reject) {
  // executor
});
Copy the code

The constructor Promise takes a function, called an executor, as an argument and passes two functions to the function as arguments: Resolve and reject.

The created Promise instance object (hereafter replaced by Promise) has the following internal properties:

  • State: indicates the state of the promise. The initial value is “pending”, which becomes “fulfilled” after the resolve call and “rejected” after the reject call.
  • Result: Indicates the result of the promise. The initial value is undefined. The value becomes value after the resolve call, and the error becomes error after the reject call.

When a New Promise is executed, executor executes immediately. Asynchronous tasks that need to be processed can be written in it, and synchronous tasks are also supported. When the task finishes processing the result, one of the following callbacks needs to be called:

  • Resolve (value) : You can pass the result of successful task execution to resolve and call it. Then the state of the promise becomes “fulfilled”.
  • Reject (Error) : You can pass an error object to reject and call it. The state of the promise changes to “Rejected”.

Resolve /reject takes only one argument (or no arguments), and excess is ignored.

An already “settled” promise (which has become “fulfilled” or “rejected”) will not be able to call resolve or reject again. That is, resolve or reject can only be called once. The remaining calls to resolve and reject are ignored.

Example:

let promise = new Promise(function(resolve, reject) {
  setTimeout((a)= > resolve("done"), 1000);
});

let promise = new Promise(function(resolve, reject) {
  setTimeout((a)= > reject(new Error("Whoops!")), 1000);
});
Copy the code

Consumer promise

Consume a promise by. Then,. Catch and. Finally.

The state and Result properties of a promise are internal and cannot be accessed directly. Instead, we can use.then/.catch/.finally.

1. then

.then(f, f) takes two function arguments: – The first function executes after promise Resolved and receives the result as an argument; – The second (not mandatory) function executes after promise rejected and takes an error;

let promise = new Promise(function(resolve, reject) {
  setTimeout((a)= > resolve("done!"), 1000);
});

promise.then(
  result= > alert(result), // "done!"
  error => alert(error) / / is not running
);
Copy the code
2. catch

Catch (f) takes a function as an argument, reject error, which is the short form of.then(null, f).

let promise = new Promise((resolve, reject) = > {
  setTimeout((a)= > reject(new Error("Whoops!")), 1000);
});

promise.catch(alert); // Error: Whoops!
Copy the code
3. finally

Finally (f) accepts a function with no arguments and executes whether the promise is resolved or rejected.

new Promise((resolve, reject) = > {
  // ...
}).finally((a)= > stop loading indicator)
  .then(result= > show result, err => show error);
Copy the code

Finally will continue to pass on the promise.

4. Execution sequence

.then/.catch/.finally Asynchronous execution: The promise will be executed immediately when the status becomes settled from pending.

Rather, when the.then/catch handler should execute, it first enters the internal queue. The JavaScript engine extracts the handler from the queue and executes setTimeout(… , 0).

In other words,.then(handler) will be triggered, which will do something similar to setTimeout(handler, 0).

In the following example, the promise is immediately resolved, so.then(alert) is triggered immediately: The alert is queued and executed as soon as the code completes.

// A promise that was resolved immediately
let promise = new Promise(resolve= > resolve("done!"));

promise.then(alert); // done! (After the current code is complete)

alert("code finished"); // The alert will be displayed first
Copy the code

So code after.then is always executed before the handler (even in the case of a pre-resolved promise).

The chain of Promise

The handler f (handler) of promise.then(f) is called and returns a promise. The value returned by the handle itself is the result of the promise; The result can be passed to the next chain of.THEN handlers. When you separate a. Then from a promise, the result is the same each time, because the. Then simply uses the result provided by the promise and does not change the original promise itself.

new Promise(function(resolve, reject) {
  setTimeout((a)= > resolve(1), 1000); / / (*)
}).then(function(result) { / / (* *)
  alert(result); / / 1
  return result * 2;
}).then(function(result) { / / (* * *)
  alert(result); / / 2
  return result * 2;
}).then(function(result) {
  alert(result); / / 4
  return result * 2;
});
Copy the code

The handler used in.then(handler) can create and return a promise. At this point, other handlers will wait for it to settle before getting the result

new Promise(function(resolve, reject) {
  setTimeout((a)= > resolve(1), 1000);
}).then(function(result) {
  alert(result); / / 1
  return new Promise((resolve, reject) = > { / / (*)
    setTimeout((a)= > resolve(result * 2), 1000);
  });
}).then(function(result) { / / (* *)
  alert(result); / / 2
  return new Promise((resolve, reject) = > {
    setTimeout((a)= > resolve(result * 2), 1000);
  });
}).then(function(result) {
  alert(result); / / 4
});
Copy the code
Thenable object

Handler can also return a “thenable” object — an arbitrary object with a method.then. It will be treated as a promise. Third-party libraries can implement their own Promise-compatible objects. They can have an extended set of methods, but are also compatible with native Promises because they implement.then methods.

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve); // function() { native code }
    // Use this. Num *2 to resolve after 1 second
    setTimeout((a)= > resolve(this.num * 2), 1000); / / (* *)}}new Promise(resolve= > resolve(1))
  .then(result= > {
    return new Thenable(result); / / (*)
  })
  .then(alert); // 2 is displayed after 1000ms
Copy the code

An object returned by the handler that has a THEN method is called immediately and takes resolve and Reject as arguments. Until resolve or reject is executed, the result is passed to the next.then, and so on down the chain.

This feature can be used to integrate custom objects with the Promise chain without having to inherit from the promise. It is recommended that asynchronous behavior always return a promise for subsequent extension of the chain.

If the.then (or catch) handler returns a promise, the rest of the chain will wait until its state becomes settled. When settled, the result (or error) will be passed on.

More promise. Then (f1, f2); And promise. Then (f1). The catch (f2);

  • The former: there is no chain, so f1 errors will not be handled
  • The latter: Error is passed along the chain as result, so any error in F1 will be handled by. Catch

The method of Promise

The Promise class has five static methods:

  • Promise.resolve(value) – Returns a resolved Promise based on a given value.
  • Promise.reject(error) – Returns a rejected Promise based on a given error.
  • Promise.all(promises) – Wait for all promises to become resolve and return an array of their results. If any given promise is reject, then it becomes an error result of Promise.all and all other results are ignored.
  • Promise.allsettled (promises) (new method) – Wait for all promises resolve or reject and return an array of their results as objects:
    • State: ‘fulfilled’ or ‘rejected’
    • This is very depressing. This is very depressing. This is very depressing.
  • Promise.race(promises) – wait for the first Promise to be resolved and the result/error is the result.

Of these five methods, promise. all is most commonly used in actual situations.

1. Promise.resolve

Let Promise = Promise.resolve(value) — Returns a resolved Promise based on the given value. Let promise = new Promise(resolve => resolve(value));

2. Promise. Reject (rarely used in practice)

Let Promise = Promise.reject(error) — Creates a rejected promise with an error. Let promise = new Promise((resolve, reject) => reject(error));

3. Promise.all

let promise = Promise.all([…promises…] ); — Execute multiple promises in parallel, returning a new promise whose result is an ordered array containing the results of all promises. The argument is an array of Promises (strictly any iterable, usually an array).

Promise.all([
  new Promise(resolve= > setTimeout((a)= > resolve(1), 3000)), / / 1
  new Promise(resolve= > setTimeout((a)= > resolve(2), 2000)), / / 2
  new Promise(resolve= > setTimeout((a)= > resolve(3), 1000))  / / 3
]).then(alert); // 1,2,3 when the promise is ready: each promise becomes a member of the array

// This is often used to send parallel requests
let names = ['iliakan'.'remy'.'jeresig'];
let requests = names.map(name= > fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
  .then(responses= > {
    // When all responses are ready, we can display the HTTP status code
    for(let response of responses) {
      alert(`${response.url}: ${response.status}`); // Each URL displays 200
    }
    return responses;
  })
  // Map response arrays to Response.json () to read their contents
  .then(responses= > Promise.all(responses.map(r= > r.json())))
  // All JSON results are parsed: "Users" is their array
  .then(users= > users.forEach(user= > alert(user.name)));
Copy the code

If any promise is reject, promise. all returns a promise that immediately rejects the error. Ignore all other promises in the list. Their results are also ignored.

Promise.all([
  new Promise((resolve, reject) = > setTimeout((a)= > resolve(1), 1000)),
  new Promise((resolve, reject) = > setTimeout((a)= > reject(new Error("Whoops!")), 2000)),
  new Promise((resolve, reject) = > setTimeout((a)= > resolve(3), 3000))
]).catch(alert); // Error: Whoops!
Copy the code

Promise.all(…) Accept an iterable promise collection (in most cases, an array). But if any of these objects is not a Promise, it will be wrapped directly into promise.resolve.

Promise.all([
  new Promise((resolve, reject) = > {
    setTimeout((a)= > resolve(1), 1000)}),2./ / as a Promise. Resolve (2)
  3  / / as a Promise. Resolve (3)
]).then(alert); / / 1, 2, 3
Copy the code
4. Promise.allSettled (recently added feature, old browsers need polyfills)

All, except that Promise.allSettled will wait for all the promises to be settled: even if one is settled, it will still wait for the others. The completed array consists of the following objects: {status:”fulfilled”, value:result} For successful responses, {status:”rejected”, reason:error} for error responses.

let urls = [
  'https://api.github.com/users/iliakan'.'https://api.github.com/users/remy'.'https://no-such-url'
];

Promise.allSettled(urls.map(url= > fetch(url)))
  .then(results= > {
    This is a pity pity, this is a pity pity, this is a pity pity, this is a pity pity, this is a pity pity, this is a pity pity, this is a pity pity, this is a pity pity, this is a pity pity 'rejected', reason: ...error object...} ]*/
    results.forEach((result, num) = > {
      if (result.status == "fulfilled") {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[num]}: ${result.reason}`); }}); });Copy the code

If your browser doesn’t support Promise.allSettled, it’s easy to make it work with Polyfill:

if(!Promise.allSettled) {
  Promise.allSettled = function(promises) {
    // p => promise.resolve (p) converts this value to a Promise (in case a non-promise is passed)
    return Promise.all(promises.map(p= > Promise.resolve(p).then(
      v= > ({ state: 'fulfilled'.value: v }), 
      r => ({ state: 'rejected'.reason: r })
    )));
  };
}
Copy the code
5. Promise.race

let promise = Promise.race(iterable); — Like promise. all, it accepts an iterable set of promises, but as soon as one Promise is settled, it stops waiting, taking the result/error of that Promise as its result, and ignoring the results/errors of all other promises.

Promise.race([
  new Promise((resolve, reject) = > setTimeout((a)= > resolve(1), 1000)),
  new Promise((resolve, reject) = > setTimeout((a)= > reject(new Error("Whoops!")), 2000)),
  new Promise((resolve, reject) = > setTimeout((a)= > resolve(3), 3000))
]).then(alert); / / 1
Copy the code

Error handling

  1. An error occurs in the Promise’s executor and handler (function arguments in.then/.catch/.finally), or a call to reject() changes the Promise to Rejected, Pass the error to the nearest error handler. It’s like there’s an invisible around the codetry.. catch.
  2. In a chained call, a.catch is added at the end of the call to handle all errors above, and as soon as there is an error, control is passed directly to the nearest.catch
  3. After the catch has handled the error, the catch can continue processing. Then
// 1. Code execution error
new Promise((resolve, reject) = > {
  throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!

// 1. Call reject actively
new Promise((resolve, reject) = > {
  reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!

// 2. Error in processor
new Promise((resolve, reject) = > {
  resolve("ok");
}).then((result) = > {
  throw new Error("Whoops!"); // Rejects this promise
}).then((result) = > {
  // The then is not executed
}).catch(alert); // Error: Whoops!

// 3. Run catch -> then
new Promise((resolve, reject) = > {
  throw new Error("Whoops!");
}).catch(function(error) {
  alert("The error is handled, continue normally");
}).then((a)= > alert("Next successful handler runs"));
Copy the code
Uncaught errors

If the promise becomes rejected, but there is no catch to handle the error, the error gets stuck and the script dies.

The JavaScript engine tracks such rejections, generates a global error, and can listen for unhandledrejection event capture.

window.addEventListener('unhandledrejection'.function(event) {
  // the event object has two special properties:
  alert(event.promise); // [object Promise] - Generates an incorrect Promise
  alert(event.reason); // Error: Whoops! - Unhandled error objects
});

new Promise(function() {
  throw new Error("Whoops!");
}); // There is no catch handling error
Copy the code

It is recommended to place.catch where you want to handle the error, define a custom error class to help analyze the error, and also to rethrow the error. If the script cannot be recovered after an error occurs, the error can be handled without catch, but the unhandledrejection event should be used to track the error. Use finally to handle tasks that must occur, such as turning off loading.

One browser trick is to return a zero-timeout promise from the finally. This is because some browsers, such as Chrome, require promise handlers outside of “a little bit of time” to draw changes to a document. So it ensures that the indication is visually stopped before entering the next step of the chain.

Unable to catch error todo

An error thrown in setTimeout cannot be caught by catch:

const promise = new Promise(function(resolve, reject) {
  setTimeout(function () { throw new Error('test')},0)}); promise.catch(function(error) { console.log(error) });
Copy the code

Unless explicitly called reject:

const promise = new Promise(function(resolve, reject) {
  setTimeout(function () { 
    reject(new Error('test'));
 }, 0)});Copy the code

Cause: the JS event loop list is divided into macro tasks and micro tasks: setTimeOut is a macro task, promise is a micro task, they have their own order of execution; So this code is executed in the following order:

  1. When the code execution stack enters the promise, the setTimeOut callback is added to the macro task queue
  2. The code executes the promise’s catch method (the microtask queue) before the setTimeOut callback has been executed
  3. The execution stack check finds that the execution of the current microtask queue is complete, and the execution of the macro task queue starts
  4. Perform throw new Error('test')The exception is actually thrown outside of the promise, but if a setTimeOut actively triggers the promise’s reject method, then the promise’s catch will find it in its microtask queue after the setTimeOut callback has executed and then execute it, so the error can be caught

Promises and microtasks

Handlers for promises are asynchronous. Then,.catch, and.finally. Asynchronous tasks require proper administration. To this end, the JavaScript standard specifies an internal queue called PromiseJobs — the “Microtasks Queue” (a V8 term). This queue is first in, first out, and the execution of the task queue is started only if no other tasks are running in the engine. When a promise is ready, its.then/catch/finally handler is queued. When the current code executes and all the previously queued handlers are complete, the JavaScript engine retrieves these tasks from the queue and executes them. Even if a promise is immediately resolved,.then,.catch, and.finally, the code after the promise is executed first. If you want to ensure that a piece of code is executed after.then/catch/finally, it is best to add it to a chained call to.then.

let promise = Promise.resolve();

promise.then((a)= > alert("promise done"));

alert("code finished"); // The warning box will pop up first
Copy the code
Rejection is not processed

Promise errors that are not processed at the end of the microTask queue. When the Microtask queue completes, the engine checks the promises and if any of them have the Rejected state, an unhandledrejection event is triggered. But if we do a catch inside setTimeout, the unhandledrejection will be fired before the catch executes, so the catch doesn’t work.

let promise = Promise.reject(new Error("Promise Failed!"));
setTimeout((a)= > promise.catch(err= > alert('caught')));
window.addEventListener('unhandledrejection', event => alert(event.reason));
// Promise Failed! -> caught
Copy the code

Promiseize the callback function

Simple example

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;
  script.onload = (a)= > callback(null, script);
  script.onerror = (a)= > callback(new Error(`Script load error for ${src}`));
  document.head.append(script);
}

// promise:
let loadScriptPromise = function(src) {
  return new Promise((resolve, reject) = > {
    loadScript(src, (err, script) => {
      if (err) reject(err)
      elseresolve(script); }); })}/ / usage:
// loadScriptPromise('path/script.js').then(...)
Copy the code

Common Promisify functions:

function promisify(f) {
  return function (. args) { // Return a wrapper function
    return new Promise((resolve, reject) = > {
      function callback(err, result) { // The custom callback for f
        if (err) {
          return reject(err);
        } else {
          resolve(result);
        }
      }

      args.push(callback); // Attach our custom callback function at the end of the argument

      f.call(this. args);// Call the original function
    });
  };
};

/ / usage:
letloadScriptPromise = promisify(loadScript); loadScriptPromise(...) .then(...) ;Copy the code

The above example of a callback function that can only take two arguments, but takes multiple arguments:

// Set this parameter to promisify(f, true) to retrieve the result array
function promisify(f, manyArgs = false) {
  return function (. args) {
    return new Promise((resolve, reject) = > {
      function callback(err, ... results) { // The custom callback for f
        if (err) {
          return reject(err);
        } else {
          // Resolve all callback results if manyArgs is specified
          resolve(manyArgs ? results : results[0]);
        }
      }

      args.push(callback);

      f.call(this. args); }); }; };/ / usage:
f = promisify(f, true); f(...) .then(arrayOfResults= >. , err => ...)Copy the code