Don't you know the Promise object of JS? Take a look

Promise object

Promise meaning

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

Promise is simply a container that holds the results of an event (usually an asynchronous operation) that will not end in the future. Syntactically speaking, promise is an object from which you can get the message of asynchronous operation. Promise provides a unified API, and various asynchronous operations can be processed in the same way.

Promise objects have the following 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, completed, and rejected. Only the result of asynchronous operation can determine the current state, and no other operation can change this state. This is also the origin of Promise. Its English meaning is "commitment", which means that other means cannot be changed.

(2) Once the state changes, it will not change again. This result can be obtained at any time. There are only two possibilities for the state of Promise object to change: from pending to fully and from pending to rejected. As long as these two situations occur, the state will solidify and will not change again. This result will be maintained all the time. At this time, it is called resolved. If the change has occurred, you can add a callback function to the Promise object and get the result immediately. This is completely different from the Event. The characteristic of the Event is that if you miss it and listen again, you won't get the result.

Note that for the convenience of writing, the resolved unified in the later part of this chapter only refers to the fully qualified state, not including the rejected state.

With Promise object, asynchronous operation can be expressed in the process of synchronous operation, avoiding layers of nested callback functions. In addition, Promise objects provide 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. It cannot be cancelled halfway. Secondly, if the callback function is not set, the errors thrown by promise will not be reflected to the outside. Third, when it is in the pending state, it is impossible to know which stage it has reached (just started or about to be completed).

If some events happen repeatedly, generally speaking, use Stream Mode is a better choice than deploying Promise.

Basic Usage

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

The following code creates a Promise instance.

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

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

The Promise constructor takes a function as an argument, 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 themselves.

The resolve function is used to change the state of Promise object from "incomplete" to "successful" (i.e. from pending to resolved), call it when the asynchronous operation is successful, and pass the result of the asynchronous operation as a parameter; The reject function is used to change the state of the Promise object from incomplete to failed (that is, from pending to rejected), call it when the asynchronous operation fails, and pass 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 callback functions of resolved state and 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 changes to resolved, and the second callback function is called when the state of the Promise object changes to rejected. Both functions are optional and do not have to be provided. They all accept the value from the Promise object as a parameter.

The following 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, representing the result that will not occur for a period of time. After the specified time (ms parameter), the state of Promise instance changes to resolved, and the callback function bound by then method will be triggered.

Promise will be executed immediately after it is created.

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, Promise is executed immediately after it is created, so Promise is output first. Then, the callback function specified by the then method will not be executed until all synchronization tasks in the current script have been executed, so the resolved is finally output.

The following is an example of loading pictures 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;
  });
}

In the above code, Promise is used to wrap an asynchronous operation of image loading. If the load is successful, the resolve method is called; otherwise, the reject method is called.

The following is an example of Ajax operation implemented with 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);
});

In the above code, getJSON is the encapsulation of 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, the resolve function and reject function are called with parameters.

If the resolve function and reject function are called with parameters, their parameters are passed to the callback function. The parameter of reject function is usually the instance of Error object, which indicates the Error thrown; In addition to 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 instances of Promise, 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 at this time, the state of p1 will be transferred to p2, that is, the state of p1 determines the state of p2. If the state of p1 is pending, the callback function of p2 will wait for the state of p1 to change; If the status of p1 is resolved or rejected, the callback function of p2 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 and becomes rejected after 3 seconds. The state of p2 changes after 1 second, and the resolve method returns p1. Since p2 returns another Promise, p2's own state is invalid. The state of p2 is determined by the state of p1. Therefore, the following then statements 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 does not terminate the execution of Promise's 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 printed first. This is because the immediately resolved Promise is executed at the end of this round of event loop, which is always later than the synchronization task of this round of loop.

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

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

Promise.prototype.then()

Promise instance has a then method, that is, the then method is defined in the prototype object promise On prototype. Its function is to add a callback function when the state changes for promise instance. As mentioned earlier, the first parameter of the then method is the callback function in the resolved state, and the second parameter is the callback function in the rejected state. They are optional.

The then method returns a new Promise instance (note that it is not the original Promise instance). Therefore, you can use the chain writing method, that is, after the then method, you can call another 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 returned result will be passed into the second callback function as a parameter.

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 (i.e. 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 the new Promise object to change. If the status changes to resolved, the first callback function will be called. If the status changes to rejected, the second callback function will be called.

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

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

Promise.prototype.catch()

Promise. prototype. The catch () method is then(null, rejection) or An alias for then(undefined, rejection), which specifies the callback function when an error occurs.

getJSON('/posts.json').then(function(posts) {
  // ...
}).catch(function(error) {
  // Error occurred when processing getJSON and the previous callback function
  console.log('An error has occurred!', error);
});

In the above code, the getJSON() method returns a Promise object. If the state of the object changes to resolved, the callback function specified by the then() method will be called; If the asynchronous operation throws an error, the status will change to rejected, and the callback function specified by the catch() method will be called to handle the 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));

Here 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, promise throws an error and is caught by the callback function specified by the catch() method. Note that the above writing is equivalent to the following two writing methods.

// Writing method I
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 2
const promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
promise.catch(function(error) {
  console.log(error);
});

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

If the Promise state has changed to resolved, it is invalid to throw an error again.

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, Promise throws an error after the resolve statement and will not be caught, which is equal to not throwing. Because once the Promise state changes, it will remain in this state forever and will not change again.

The error of Promise object is "bubbling" and will be passed back until it is caught. That is, errors are 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) {
  // Handle the errors generated by the first three promises
});

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

Generally speaking, don't define the callback function of Reject status (that is, the second parameter of 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
  });

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

Different from the traditional try/catch code block, if the catch() method is not used to specify the callback function for error handling, 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 following line will report an error because x has no statement
    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 someAsyncThing() function has an internal syntax error. When the browser runs to this line, it will print the error prompt ReferenceError: x is not defined, but it will not exit the process and terminate the script execution. After 2 seconds, it will still output 123. That is to say, the internal error of Promise will not affect the external code of Promise. The popular saying is "Promise will eat the error".

This script is executed on the server, and the exit code is 0 (indicating successful execution). However, node JS has an unhandledRejection event, which specifically listens for uncapped reject errors. The above script will trigger the listening function of this event, which can throw errors in the listening function.

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

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

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

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

In the above code, Promise specifies to throw an error in the next "event loop". By that time, the Promise operation has ended, so this error is thrown outside the Promise function, which will bubble to the outermost layer and become an uncapped error.

It is always recommended that Promise objects be followed by catch() methods to handle errors that occur within Promise. 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 following line will report an error because x has no statement
    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, the above code skips the catch() method and directly executes the following then() method. At this time, if an error is reported in the then() method, it has nothing to do with the previous catch().

In the catch() method, you can throw more errors.

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // The following line will report an error because x has no statement
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // The following line will report an error because y has no statement
  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 are no other catch() methods, so this error will not be captured or 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 following line will report an error because y has no statement
  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 the standard introduced by ES2018.

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

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

The following is an example. The server uses Promise to process the request, and then uses the finally method to turn off 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 state is fully or rejected. This shows that the operations in the finally method should be state independent and independent of the Promise execution result.

finally is essentially a special case of the then method.

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

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

In the above code, if the finally method is not used, the same statement needs to be written once for success and once for 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, no matter whether the previous Promise is fully or rejected, the callback function callback will be executed.

From the above implementation, we can also see 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()

Promise. The 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, Promise The all () method accepts an array as a parameter. p1, p2 and p3 are Promise instances. If they are not, Promise will be called first Resolve method, convert the parameter to Promise instance, and then conduct further processing. In addition, Promise The parameter of the all () method can 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 can be divided into two cases.

(1) Only when the states of p1, p2 and p3 become fully, the state of p will become fully. At this time, the return values of p1, p2 and p3 form an array and are passed to the callback function of p.

(2) As long as one of p1, p2 and p3 is rejected, the state of p becomes rejected. At this time, the return value of the first rejected instance will be passed to the callback function of p.

Here 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 six promise instances. Promise will be called only when the state of these six instances becomes fully or one of them becomes rejected Callback function after all method.

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 not be triggered until their results are returned.

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

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

const p2 = new Promise((resolve, reject) => {
  throw new Error('Wrong report');
})
.then(result => result)
.catch(e => e);

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

In the above code, p1 will be resolved and p2 will be rejected first, but p2 has its own catch method, which returns a new Promise instance, and p2 actually points to this instance. After the catch method is executed, the instance will also become resolved, resulting in Promise Both instances in the all () method parameter will be resolved, so the callback function specified by the then method will be called instead of the callback function specified by the catch method.

If p2 does not have its own catch method, promise will be called Catch method of all().

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

const p2 = new Promise((resolve, reject) => {
  throw new Error('Wrong report');
})
.then(result => result);

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

Promise.race()

Promise. The 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 the state first, the state of p will change accordingly. The return value of the Promise instance that changed first is passed to the callback function of p.

Promise. The parameters of race() method are the same as promise Like the all () method, if it is not a promise instance, it will first call promise Resolve () method, convert the parameter to promise instance, and then further processing.

The following is an example. If no result is obtained within the specified time, change the Promise status to reject, otherwise change 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 cannot return the result within 5 seconds, the state of variable p will change to rejected, thus triggering the callback function specified by the catch method.

Promise.allSettled()

Promise. The allsettled () method takes a set of promise instances as parameters and wraps them into a new promise instance. The wrapper instance will not end until all of these parameter instances return results, whether fully or rejected. The method is provided by ES2020 introduce.

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. When all three requests are completed, the loaded scroll icon will disappear whether the request succeeds or fails.

Once the new promise instance returned by this method is completed, the state is always fully and will not become rejected. After the status changes to full, the parameters received by promise's listening function are an array, and each member corresponds to one passed in promise Promise instance of 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, Promise The return value of allsettled() is allSettledPromise, and the status can only become fully. The parameter received by its listening function is array results. Each member of the array is an object, corresponding to the incoming Promise Two Promise instances of allsettled(). Each object has a status attribute, and the value of this attribute can only be the string fully or the string rejected. When fully, the object has a value attribute, and when rejected, it has a 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);

// Filter out successful requests
const successfulPromises = results.filter(p => p.status === 'fulfilled');

// Filter out the 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 have ended. At this time, promise The allsettled () method is useful. Without this method, it is troublesome to ensure that all operations are completed. Promise. The all () method cannot 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, and other requests may not have ended.');
}

In the above code, promise All() cannot determine that all requests have ended. To achieve this goal, it is very troublesome to write, with promise Allsettled(), which is easy.

Promise.any()

ES2021 introduces Promise.any() method . Accept the parameter as a new instance of Promise method. As long as one of the parameter instances is in the full state, the packaging instance will be in the full state; If all parameter instances become rejected, the wrapper instance will become rejected.

Promise.any() and promise The race () method is very similar, except that it will not end because a promise becomes rejected.

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, Promise The parameter array of the any () method contains three Promise operations. As long as one of them becomes full, Promise The Promise object returned by any () becomes fully. If all three operations become rejected, the await command throws an error.

Promise. The error thrown by 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 rejected operation. 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 try 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.
  }
);

Here 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 you need to turn an existing object into a Promise object, Promise The resolve () method does just that.

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.

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

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

(1) Parameter is a Promise instance

If the parameter is a Promise instance, Promise Resolve will return the instance intact without any modification.

(2) Parameter is a thenable object

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

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

Promise. The resolve () method turns this object into a promise object, and then immediately executes 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 thenable object is executed, the state of object p1 changes to resolved, so the callback function specified by the last then() method is executed immediately and output 42.

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

Promise. If the parameter is a raw value or an object without a then() method The resolve () method returns a new promise object with the status of resolved.

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

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

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

(4) Without any parameters

Promise. The resolve () method allows you to directly return a promise object in the resolved state without parameters.

Therefore, if you want to get a Promise object, a more convenient method is to call Promise directly 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 immediate resolve() is executed at the end of this round of event loop, not at the beginning of the next round of 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 "event loop", promise Resolve() is executed at the end of this round of "event loop", console Log ('one ') is executed immediately, so it is output first.

Promise.reject()

Promise. The reject (reason) method will also return a new promise instance whose status is rejected.

const p = Promise.reject('Error ');
// Equivalent to
const p = new Promise((resolve, reject) => reject('Error '))

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

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

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

Promise.reject('Error ')
.catch(e => {
  console.log(e === 'Error ')
})
// true

In the above code, promise The parameter of reject () method is a string, which is followed by the parameter e of catch() method.

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, a Promise object is usually returned.

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 the Promise object and call the next method.

Promise.try()

In actual development, we often encounter a situation: we don't know or don't want to distinguish whether function f is synchronous function or asynchronous operation, but we want to deal with it with Promise. Because in this way, whether f contains asynchronous operations or not, the next process can be specified with the then method, and the catch method can be used to deal with the errors thrown by F. Generally, the following writing method will be adopted.

Promise.resolve().then(f)

One disadvantage of the above method is that if f is a synchronization function, 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, function f is synchronous, but after being 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 method is to use async function to write.

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

In the above code, the second line is an anonymous function that will be executed immediately. The async function inside will be executed immediately. Therefore, if f is synchronized, the synchronization result will be obtained; If f is asynchronous, then you can use then to specify the next step, as described below.

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

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

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

The second way 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 the immediately executed anonymous function to execute new Promise(). In this case, the synchronization function is also executed synchronously.

Since this is a very common requirement, there is now one proposal , provide promise The try method replaces the above writing.

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

In fact, Promise Try has existed for a long time, Promise Library Bluebird,Q and when , this method has long been provided.

Because promise Try provides a unified processing mechanism for all operations, so if you want to use the then method to manage the process, you'd better use promise Try to wrap it. So there Many benefits , one of them is that you can 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 catch it with the catch method, as written below.

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). At this time, you have to use try Catch to catch.

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

The above way of writing is very clumsy. At this time, promise can be used uniformly Catch () captures all synchronous and asynchronous errors.

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

In fact, promise Try is a block of code that simulates try, like promise Catch simulates a catch code block.

Keywords: Javascript

Added by scerm on Fri, 18 Feb 2022 21:58:32 +0200