The meaning of the Promise


Promise is a solution to asynchronous programming that makes more sense and is more powerful than traditional solutions — callback functions and events. It was first proposed and implemented by the community, and ES6 has written it into the language standard, unifying usage, and providing Promise objects natively.

A Promise is simply a container that holds the result of an event (usually an asynchronous operation) that will end in the future. Syntactically, a Promise is an object from which to get messages for asynchronous operations. Promise provides a uniform API, and all kinds of asynchronous operations can be handled in the same way.

Promise objects have two characteristics.

(1) The state of the object is not affected by the outside world. The Promise object represents an asynchronous operation with three states: Pending, fulfilled and Rejected. Only the result of an asynchronous operation can determine the current state, and no other operation can change the state. That’s where the name “Promise” comes from. Its English name means “Promise,” indicating that nothing else can change it.

(2) Once the state changes, it will never 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 depressing and from pending to Rejected. As long as these two things are happening the state is fixed, it’s not going to change, it’s going to stay the same and that’s called resolved. If the change has already occurred, you can add a callback to the Promise object and get the same result immediately. This is quite different from an Event, which has the characteristic that if you miss it and listen again, you will not get the result.

Note that in order to facilitate the writing, the “resolved” in this chapter only refers to the regrettable state, excluding the rejected state.

With the Promise object, asynchronous operations can be expressed as a flow of synchronous operations, avoiding layers of nested callback functions. In addition, Promise objects provide a unified interface that makes it easier to control asynchronous operations.

Promise also has some downsides. First, there is no way to cancel a Promise; once it is created, it is executed immediately and cannot be cancelled halfway through. Second, if you don’t set a callback function, errors thrown inside a Promise won’t be reflected externally. Third, when you are in a pending state, you have no way of knowing what stage of progress you are currently in (just beginning or just finishing).

If certain events are repeated over and over again, using the Stream pattern is generally a better choice than deploying promises.

Basic usage

ES6 specifies that a Promise object is a constructor that generates a Promise instance.

The following code creates an instance of Promise.

const promise = new Promise(function(resolve, reject) { // ... Resolve (value); resolve(value); } else { reject(error); }});Copy the code

The Promise constructor takes a function as an argument, resolve and reject. They are two functions that are provided by the JavaScript engine and do not need to be deployed themselves.

The resolve function changes the state of the Promise object from “unfinished” to “successful.” It will be called when the asynchronous operation succeeds and will pass the result of the asynchronous operation as an argument. The Reject function changes the state of the Promise object from “unfinished” to “failed” (i.e., from Pending to Rejected). It is called when the asynchronous operation fails and passes the error reported by the asynchronous operation as a parameter.

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

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

The then method can take two callback functions as arguments. The first callback is called when the Promise object’s state changes to Resolved. The second callback is called when the Promise object’s state changes to Rejected. Both of these functions are optional and do not have to be provided. They all accept the value passed out from the Promise object as an argument.

Here 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);
});
Copy the code

In the code above, the timeout method returns an instance of a Promise, representing a result that will occur some time later. When the state of the Promise instance changes to Resolved after the specified time (the MS parameter), the callback function bound to the then method is triggered.

Promises are implemented as soon as they are created.

let promise = new Promise(function(resolve, reject) { console.log('Promise'); resolve(); }); promise.then(function() { console.log('resolved.'); }); console.log('Hi! '); // Promise // Hi! // resolvedCopy the code

In the code above, the Promise is executed immediately after it is created, so the Promise is printed first. Then, the callback specified by the then method will not execute until all synchronization tasks in the current script have finished, so resolved will output.

Here is an example of loading images asynchronously.

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;
  });
}
Copy the code

In the code above, an asynchronous image loading operation is wrapped with Promise. If the load succeeds, the resolve method is called, otherwise the Reject method is called.

Here 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(' error ', error); });Copy the code

In the code above, getJSON is a wrapper around the XMLHttpRequest object that makes an HTTP request for JSON data and returns a Promise object. Note that inside getJSON, both resolve and reject are called with arguments.

If resolve and reject are called with arguments, their arguments are passed to the callback function. The reject function usually takes an instance of the Error object, representing the Error thrown; The arguments to the resolve function may be another Promise instance in addition to the normal value, as shown below.

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

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

In the code above, p1 and p2 are both instances of Promise, but P2’s resolve method takes P1 as an argument, meaning that the result of an asynchronous operation is the return of another asynchronous operation.

Notice that the state of P1 is passed to p2, that is, the state of P1 determines the state of P2. If P1’s state is pending, p2’s callback waits for p1’s state to change. If P1 is in resolved or Rejected, the P2 callback 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
Copy the code

In the code above, P1 is a Promise. After 3 seconds, it changes to Rejected. The state of p2 changes after 1 second, and the resolve method returns P1. Since P2 returned another Promise, p2’s own state became invalid, and p1’s state determined P2’s state. Therefore, subsequent then statements become for the latter (P1). After another 2 seconds, P1 changes to Rejected, triggering the callback specified by the catch method.

Note that calling resolve or Reject does not terminate the execution of the Promise’s argument function.

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

In the code above, after calling resolve(1), console.log(2) is executed and printed first. This is because immediate Resolved promises are executed at the end of this cycle and are always later than the synchronization task in this cycle.

In general, a call to resolve or reject completes a Promise, and subsequent actions should be placed in the then method rather than directly after resolve or reject. Therefore, it is best to precede them with a return statement so there are no surprises.

new Promise((resolve, reject) => { return resolve(1); // The following statement does not execute console.log(2); })Copy the code

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 resolved state callback and the second argument is the Rejected state callback, both of which are optional.

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

Promise.prototype.catch()

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 the previous callback function running error 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)); => console.log(' pity :', val)). Then (null, (err) => console.log("rejected:", err)); // This is a big pity.Copy the code

Here’s 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.

// const promise = new promise (function(resolve, reject) {try {throw new Error('test'); } catch(e) { reject(e); }}); promise.catch(function(error) { console.log(error); }); // const promise = new promise (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 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
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 the Promise object are “bubbling” 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) {//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().

In general, 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 traditional try/catch blocks, if there is no callback that uses the catch() method to specify error handling, the errors thrown by the Promise object are not passed to the outer code, that is, there is no response.

Const someAsyncThing = function() {return new Promise(resolve(x + 2)); }); }; someAsyncThing().then(function() { console.log('everything is great'); }); setTimeout(() => { console.log(123) }, 2000); // Uncaught (in promise) ReferenceError: x is not defined // 123Copy the code

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.”

The script is executed on the server with an exit code of 0 (indicating successful execution). However, Node.js has a unhandledRejection event that listens for uncaught reject errors, and the script above triggers a listener for this event that can throw an error in the listener.

process.on('unhandledRejection', function (err, p) {
  throw err;
});
Copy the code

In the code above, the listener function for the unhandledRejection event takes two arguments, the first an error object and the second an error Promise instance, which can be used to learn about the environment in which the error occurred.

Note that Node has plans to abolish the unhandledRejection event in the future. If there are uncaught errors within the Promise, the process is terminated directly, and the exit code for the process is not zero.

Look at the following example.

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
Copy the code

In the code above, the Promise specifies that the error will be thrown in the next “event loop.” At that point, the Promise is done running, so the error is thrown outside the Promise function and bubbles to the outermost layer as an uncaught error.

It is always recommended that a catch() method be followed by a Promise object to handle errors that occur within a Promise. The catch() method still returns a Promise object, so you can call the then() method later.

Const someAsyncThing = function() {return new Promise(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 onCopy the code

The code above runs the callback specified by the catch() method and then() method. If no error is reported, the catch() method is skipped.

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

The code above skips the catch() method and executes the later then() method because it does not report an error. Catch (); catch(); catch(); catch();

The catch() method also throws an error.

Const someAsyncThing = function() {return new Promise(resolve(x + 2)); }); }; 21 someAsyncThing().then(function() { return someOtherAsyncThing(); }).catch(function(error) { console.log('oh no', error); // The following line is an error because y does not declare y + 2; }).then(function() { console.log('carry on'); }); // oh no [ReferenceError: x is not defined]Copy the code

In the code above, the catch() method throws an error, and because there are no other catch() methods behind it, the error will not be caught and will not be passed to the outer layer. If I were to rewrite it, it would be different.

someAsyncThing().then(function() { return someOtherAsyncThing(); }).catch(function(error) { console.log('oh no', error); // The following line is an error because y does not declare y + 2; }).catch(function(error) { console.log('carry on', error); }); // oh no [ReferenceError: x is not defined] // carry on [ReferenceError: y is not defined]Copy the code

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

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.

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.

Here is an example where the server uses Promise to handle the request and then uses the finally method to shut down the server.

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

The callback function of the finally method accepts no parameters, which means there is no way to know whether the Promise state is fulfilled or rejected. This suggests that operations in the finally method should be state independent and not depend on the result of a Promise’s execution.

Finally is essentially a special case of the THEN method.

Promise.finally (() => {// statement}); Then (result => {// statement return result; }, error => {// throw error; });Copy the code

In the code above, without using the finally method, the same statement would have to be written once for both success and failure. With the finally method, you only need to write once.

Its implementation is also 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 })
  );
};
Copy the code

This code will implement the callback function regardless of whether the Promise is fulfilled or Rejected.

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

// use undefined promise.resolve (2). Then (() => {}, () => {}) // use undefined promise.reject ().reject() => {}). Then () => {}, () = > {}) / / reject a value of 3 Promise. Reject (3) finally (() = > {})Copy the code

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]);
Copy the code

In the code above, the promise.all () method takes an array of arguments. P1, p2, and p3 are all Promise instances. If they are not, the Promise. In addition, the promise.all () method can take arguments that are not arrays, but must have an Iterator interface and return each member as a Promise instance.

The state of P is determined by P1, P2 and P3, which can be divided into two cases.

(1) Only when the states of P1, P2 and P3 become depressing, the state of P will become depressing. At this time, the return values of P1, P2 and P3 will form an array and be passed to the callback function of P.

(2) As long as p1, P2 and P3 are rejected, P becomes rejected, and the return value of the first rejected instance is passed to p’s callback function.

Here is a concrete example.

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){ // ... });Copy the code

Promises are an array of six Promise instances. Only when the states of the six Promise instances become fulfilled or one of them becomes Rejected, will the callback function behind Promise.

Here’s 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));
Copy the code

In the code above, booksPromise and userPromise are two asynchronous operations and the pickTopRecommendations callback will not be triggered until both of their results are returned.

Note that if a Promise instance that is a parameter defines its own catch method, it does not fire the promise.all () catch method once it is rejected.

const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result) .catch(e => e); Const p2 = new Promise((resolve, reject) => {throw new Error(' Error '); }) .then(result => result) .catch(e => e); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); // ["hello", Error: Error]Copy the code

P2 has its own catch method, which returns a new Promise instance that P2 actually refers to. This instance will also become Resolved after executing the catch method, causing both instances in promise.all () to call the then callback and not the catch callback.

If P2 does not have its own catch method, the catch method of promise.all () is called.

const p1 = new Promise((resolve, reject) => { resolve('hello'); }) .then(result => result); Const p2 = new Promise((resolve, reject) => {throw new Error(' Error '); }) .then(result => result); Promise.all([p1, p2]) .then(result => console.log(result)) .catch(e => console.log(e)); // Error: an Error is reportedCopy the code

Promise.race()

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

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

In the above code, the state of P changes as long as one of the first instances of P1, P2, and P3 changes state. The return value of the first changed Promise instance is passed to p’s callback.

The parameters of the promise.race () method are the same as those of the promise.all () method. If it is not a Promise instance, the promise.resolve () method, described below, is first called to turn the parameters into a Promise instance, and then further processing.

Here’s 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 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();
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); // this is a big pity; // Const promise = results. Filter (p => p.status === 'depressing '); Const errors = results.filter (p => p.status === 'rejected').map(p => p.reon); // Error = results.filter (p => p.status === 'rejected').map(p => p.reon);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, 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.

Promise.any()

ES2021 introduces the promise.any () method. The method takes a set of Promise instances as parameters and returns them wrapped as a new Promise instance. As long as one parameter instance becomes a depressing state, the packaging instance will become a depressing state. If all parameter instances become the Rejected state, the wrapper instance becomes the Rejected state.

Promise.any() is like the promise.race () method except that it does not end when a Promise changes to 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);
}
Copy the code

In the code above, the parameter array for the promise.any () method contains three Promise operations. This is a big pity. As long as one of them becomes a big pity, the Promise. Any () will return a Promise. If all three operations become Rejected, the await command throws an error.

The error thrown by promise.any () is not a generic error but an AggregateError instance. It is equivalent to an array, with each member corresponding to an error thrown by the Rejected operation. Here is an example implementation 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;
Copy the code

If you don’t use try… The catch structure and await command can be written as follows.

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

Here’s 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]
});
Copy the code

Promise.resolve()

Sometimes you need to turn an existing object into a Promise object, and the promise.resolve () method does this.

const jsPromise = Promise.resolve($.ajax('/whatever.json'));
Copy the code

The code above converts the jquery-generated Deferred object into a new Promise object.

Promise.resolve() is equivalent to the following.

Resolve ('foo') // equivalent to new Promise(resolve => resolve('foo'))Copy the code

The arguments to the promise.resolve () method are divided into four cases.

(1) The argument is an instance of Promise

If the argument is a Promise instance, promise.resolve will return the instance unchanged.

(2) The parameter is onethenableobject

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 turns this object into a Promise object and 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.

(3) The parameter does not havethen()Method object, or not 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) Without 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 (' error '); // const p = new Promise((resolve, reject) => reject(' error ')) p.hen (null, function (s) {console.log(s)}); / / make a mistakeCopy the code

The above code generates an instance P of the Promise object in the rejected state, and the callback is executed immediately.

The arguments of the promise.reject () method are left as reject arguments and become arguments for subsequent methods.

Promise. Reject (the 'wrong'). The catch (e = > {the console. The log (e = = = 'wrong')}) / / trueCopy the code

In the code above, the promise.reject () method takes a string, followed by the catch() method’s argument, e.

application

Loading pictures

We can write the loading of the image as a Promise, and once the loading is complete, the state of the Promise changes.

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

A combination of Generator functions and promises

The process is managed using a Generator function, which typically returns a Promise object when an asynchronous operation occurs.

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);Copy the code

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

Promise.try()

In practice, you often encounter situations where you don’t know, or don’t want to distinguish, whether function F is a synchronous function or an asynchronous operation, but you want to use promises to handle it. This allows the then method to specify the next step in the flow, regardless of whether f contains an asynchronous operation, and the catch method to handle any errors thrown by F. This is the way you would normally write it.

Promise.resolve().then(f)
Copy the code

One disadvantage of this notation is that if f is a synchronous function, it will be executed at the end of the current event loop.

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
Copy the code

In the code above, function F is synchronous, but with the Promise wrapper, it is executed asynchronously.

So is there a way to have synchronous functions execute synchronously and asynchronous functions execute asynchronously and have a unified API for them? The answer is yes, and there are two ways to write it. The first way to write it is async.

const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
Copy the code

In the above code, the second line is an immediate anonymous function that executes the async function immediately, so if f is synchronous, the result is synchronized; If f is asynchronous, then can be used to specify the next step, as follows.

(async () => f())()
.then(...)
Copy the code

Note that async () => f() eats up any errors thrown by f(). So, if you want to catch an error, use the promise.catch method.

(async () => f())()
.then(...)
.catch(...)
Copy the code

The second way is to use new Promise().

const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next
Copy the code

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

Since this is a common requirement, there is now a proposal that provides the promise.try method instead.

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
Copy the code

In fact, promise.try has been around for a long time. The Promise libraries Bluebird, Q, and When have long provided this method.

Because promise.try provides a uniform processing mechanism for all operations, it is best to wrap everything in promise.try if you want to manage processes with then methods. This has many benefits, one of which is better exception management.

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

In the above code, database.Users.get () returns a Promise object that can be caught using the catch method if an asynchronous error is thrown, as shown below.

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

But database.users.get() may also throw synchronization errors (such as database connection errors, depending on the implementation), in which case you have to try… Catch, catch, catch

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

This is unwieldy, so you can uniformly use promise.catch() to catch all synchronous and asynchronous errors.

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

In fact, promise. try simulates a try block, just as promise.catch simulates a catch block.