Promise object

  1. The meaning of the Promise
  2. Basic usage
  3. Promise.prototype.then()
  4. Promise.prototype.catch()
  5. Promise.all()
  6. Promise.race()
  7. Promise.resolve()
  8. Promise.reject()
  9. Two useful additional methods
  10. application
  11. Async function

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 and has three states: Pending, Resolved, 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 a Promise object to change state: from Pending to Resolved and from Pending to Rejected. As long as those two things happen, the state is frozen, it’s not going to change, it’s going to stay the same. If you add a callback to the Promise object, you’ll get the same result immediately, even if the change has already occurred. 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.

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.

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

Once the Promise instance is generated, the THEN method can be used to specify the Resolved and Reject state callbacks, respectively.

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

The then method can take two callback functions as arguments. The first callback is called when the state of the Promise object becomes Resolved, and the second is called when the state of the Promise object becomes Reject. The second function is optional and does not have to be provided. Both of these functions accept as arguments a value passed from the Promise object.

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, a Promise is executed immediately after it is created, so “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” is the last to output.

Here is an example of loading images asynchronously.

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    var 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

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

var getJSON = function(url) { var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler() { if ( this.readyState ! == 4 ) { return; } if (this.status === 200) { resolve(this.response); } else { reject(new Error(this.statusText)); }}; }); 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 argument to the resolve function may be another Promise instance in addition to the normal value, indicating that the result of an asynchronous operation could be either a value or another asynchronous operation, as shown below.

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

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

var p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})
var p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})
p2.then(result => console.log(result))
p2.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. P2’s state is determined by P1. After 1 second, P2 calls the resolve method, but p1’s state has not changed, so p2’s state will not change. P1 = rejected and P2 = rejected after 2 seconds.

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 callback in the Resolved state and the second argument (optional) is the callback in the Rejected state.

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 funcA(comments) {
  console.log("Resolved: ", comments);
}, function funcB(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 funcA if it becomes Resolved and funcB if the state 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) that specifies the callback when an error occurs.

getJSON("/posts.json").then(function(posts) { // ... }). Catch (function(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.

p.then((val) => console.log("fulfilled:", val)) .catch((err) => console.log("rejected:", err)); => console.log(depressing :", val)). Then (null, (err) => console.log("rejected:", err)); // This is a big pity.Copy the code

Here’s an example.

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

Var promise = new promise (function(resolve, reject) {try {throw new Error('test'); } catch(e) { reject(e); }}); promise.catch(function(error) { console.log(error); }); Var promise = new promise (resolve, reject) {reject(new Error('test')); }); promise.catch(function(error) { console.log(error); });Copy the code

Compare the two, and you 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.

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

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.

As a general rule, do not define a Reject callback (the second argument to a THEN) in a THEN method. Always use catch.

// 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 is closer to synchronous (try/catch) writing.

Unlike traditional try/catch blocks, if there is no callback that specifies error handling using the catch method, the errors thrown by the Promise object are not passed to the outer code, that is, there is no response.

Var someAsyncThing = function() {return new Promise(resolve(x + 2)); }); }; someAsyncThing().then(function() { console.log('everything is great'); });Copy the code

In the code above, the Promise object generated by someAsyncThing will report an error, but since no catch method is specified, the error will not be caught and will not be passed to the outer code, resulting in no output after running. Note that Chrome does not comply with this rule and will throw “ReferenceError: x is not defined”.

var 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.” The catch statement bubbles to the outermost layer as an uncaught error. Because at this point, the body of the Promise function is finished running, this error is thrown outside the Promise function.

Node.js has an unhandledRejection event that listens for uncaught Reject errors.

process.on('unhandledRejection', function (err, p) {
  console.error(err.stack)
});
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 the catch method still returns a Promise object, so you can call the then method later.

Var 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 the callback specified by the later 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

This code skips the catch method and executes the later then method because it does not report an error. If an error is reported in the then method, the catch is not related.

In a catch method, you can throw an error.

Var someAsyncThing = function() {return new Promise(resolve(x + 2)); }); }; 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 above code, 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 the error thrown by the previous catch method.

Promise.all()

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

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

In the code above, the promise. all method takes an array of parameters. P1, P2, and p3 are all instances of the Promise object. If they are not, the Promise. (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.

Var 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 = databaseProimse
  .then(findAllBooks);

const userPromise = databasePromise
  .then(getCurrentUser);

Promise.all([
  booksPromise,
  userPromise
])
.then(([books, user]) => pickTopRecommentations(books, user));
Copy the code

In the above code, booksPromise and userPromise are two asynchronous operations. PickTopRecommentations are triggered only when their results are returned.

Promise.race()

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

var 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 convert the parameters to 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.

var p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
])
p.then(response => console.log(response))
p.catch(error => console.log(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.resolve()

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

var 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 parameters of 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 converts this object to a Promise, and then 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 havethenMethod 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.

var 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 (determined by the fact that it is not an object with a THEN method), the return Promise instance state is Resolved from lifetime achievement, 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 you to call an Resolved Promise object with no arguments.

So, if you want to get a Promise object, it’s convenient to call the promise.resolve method directly.

var p = Promise.resolve();

p.then(function () {
  // ...
});
Copy the code

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

Promise.reject()

The promise.Reject (Reason) method also returns a new Promise instance with a state of Rejected. Its argument usage is identical to the promise.resolve method.

Var p = promise.reject (' error '); Var 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.

Two useful additional methods

The ES6 Promise API doesn’t offer many methods, and there are some useful ones you can deploy yourself. Here’s how to deploy two useful methods that are not in ES6.

done()

The callback chain of a Promise object, whether ending in a then or catch method, may not be caught if the last method throws an error (because errors within a Promise do not bubble up globally). Therefore, we can provide a done method that is always at the end of the callback chain, guaranteed to throw any errors that might occur.

asyncFunc()
  .then(f1)
  .catch(r1)
  .then(f2)
  .done();
Copy the code

The implementation code is fairly simple.

Promise.prototype.done = function (onFulfilled, onRejected) { this.then(onFulfilled, OnRejected). Catch (function (reason) {// Throw a global error setTimeout(() => {throw reason}, 0); }); };Copy the code

As can be seen from the code above, the done method can be used like the then method, providing the callback function with the Fulfilled and Rejected states, or without providing any parameters. Either way, done catches any possible errors and throws them out globally.

finally()

The finally method is used to specify actions that will be performed regardless of the final state of the Promise object. The biggest difference from the done method is that it takes as an argument a normal callback function that must be executed anyway.

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(0)
  .then(function () {
    // run test
  })
  .finally(server.stop);
Copy the code

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.

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) {
    var 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'); }); } var g = function* () { try { var foo = yield getFoo(); console.log(foo); } catch (e) { console.log(e); }}; function run (generator) { var 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.

Async function

Async functions, like Promise and Generator functions, are used to replace callback functions and solve asynchronous operations. It is essentially a syntactic sugar for Generator functions. Async functions are not included in ES6 but in ES7, but transcoders such as traceur, babel. js, and Regenerator already support this function and can be used immediately after transcoding.

For a detailed description of async functions, see the chapter Asynchronous Operations.