I. What is Promise and what problem do we use Promise to solve?
Promise is a solution for asynchronous programming: Syntactically, a promise is an object from which you can get messages for asynchronous operations; Intrinsically, it is a promise, promising that it will give you a result after a certain period of time. Promise has three states: pending (waiting state), fullfiled (success state), rejected (failure state); once the state is changed, it will not be changed again. After creating a promise instance, it will be executed immediately.
I’m sure people write code like this all the time:
function fn1(a, fn2) {
if (a > 10 && typeof fn2 == 'function') {
fn2()
}
}
fn1(11, function() {
console.log('this is a callback')
})
Generally speaking, we will encounter the callback nesting is not a lot, generally one to two levels, but in some cases, callback nesting a lot of code will be very cumbersome, will bring us a lot of trouble programming, this situation is commonly known as – callback hell.
That’s where our promises come in.
Promise is used to solve two problems:
Callback hell, the code is difficult to maintain, and often the output of the first function is the input of the second function.
Promise can support multiple concurrent requests and fetch data from concurrent requests.
This promise solves the problem of asynchrony, and by itself you can’t say that the promise is asynchronous
Second, es6 promise usage
Promise is a constructor with all, reject, and resolve on its own, and then, catch, and other equally familiar methods on its prototype.
Then create a new one.
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success!!');
}, 2000);
});
The constructor of a Promise takes one argument: a function, and this function needs to be passed two arguments:
- resolve : the callback function when the asynchronous operation succeeds.
- reject: the callback function when an asynchronous operation fails to execute.
then Usage of Chaining
So, on the surface, Promise is just able to simplify the writing of layers of callbacks, but in essence, the essence of Promise is “state”, with the maintenance of state, passing state to make the callback function can be called in a timely manner, it is much simpler and more flexible than passing the callback function. So the right scenario for using Promise is this:
p.then((data) => {
console.log(data);
})
.then((data) => {
console.log(data);
})
.then((data) => {
console.log(data);
});
Usage of reject.
Set the state of the Promise to rejected so that we can catch it in then and execute the callback for the “failed” case. Look at the code below.
let p = new Promise((resolve, reject) => {
setTimeout(function(){
var num = Math.ceil(Math.random()*10);
if(num<=5){
resolve(num);
}
else{
reject('The numbers are too big.');
}
}, 2000);
});
p.then((data) => {
console.log('resolved',data);
},(err) => {
console.log('rejected',err);
}
);
The then method can take two arguments, the first corresponds to the resolve callback and the second corresponds to the reject callback. So we are able to get the data passed to them separately. Run this code multiple times and you will get the following two random results:
Usage of catch
We know that the Promise object has a catch method in addition to the then method, what is it for? Actually, it is the same as the second parameter of then, which is used to specify the callback of reject. The usage is like this:
p.then((data) => {
console.log('resolved',data);
}).catch((err) => {
console.log('rejected',err);
});
The effect is the same as writing it in the second parameter of then. But it also has another effect: when executing the resolve callback (which is the first parameter in the then above), if an exception is thrown (a code error), then instead of reporting the error and jamming the js, it will go into the catch method. See the code below:
p.then((data) => {
console.log('resolved',data);
console.log(somedata);
})
.catch((err) => {
console.log('rejected',err);
});
In the resolve callback, we console.log(somedata); and the variable somedata is undefined. If we hadn’t used Promise, the code would have run straight to this point and reported an error on the console, without going any further. But here, we get this result:
That means it goes into the catch method and passes the reason for the error into the reason parameter. Even if there is wrong code, it won’t report the error anymore, which is the same function as our try/catch statement
Usage of all: The callback is executed by whoever is running slowly. all takes an array of parameters, the values of which are eventually counted as returned Promise objects.
Promise’s all method provides the ability to execute asynchronous operations in parallel and execute the callback only after all asynchronous operations have been executed. Look at the following example:
let Promise1 = new Promise(function(resolve, reject){})
let Promise2 = new Promise(function(resolve, reject){})
let Promise3 = new Promise(function(resolve, reject){})
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(funciton(){
}, function(){
})
With all, you can perform multiple asynchronous operations in parallel and handle all the return data in one callback, isn’t that cool? There is one scenario where this would be good to use, some applications with a lot of game-like material, where you open a web page and preload all the resources you need to use such as images, flash, and all kinds of static files. After all of them are loaded, we then initialize the page.
Usage of race: Whoever runs faster executes the callback.
Scenarios for using race: For example, we can use race to set a timeout for an asynchronous request and perform the corresponding operation after the timeout, the code is as follows:
function requestImg(){
var p = new Promise((resolve, reject) => {
var img = new Image();
img.onload = function(){
resolve(img);
}
img.src = 'imgUrl';
});
return p;
}
function timeout(){
var p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('timeout');
}, 5000);
});
return p;
}
Promise.race([requestImg(), timeout()]).then((data) =>{
console.log(data);
}).catch((err) => {
console.log(err);
});
The requestImg function requests an image asynchronously, and I’ve written the address as “path to the image”, so I’m sure it won’t be successfully requested. the timeout function is an asynchronous operation with a delay of 5 seconds. We put these two functions that return Promise objects into race, so they will race, if the image is successfully requested within 5 seconds, then all the way to then method, to perform the normal process. If the picture is not successfully returned in 5 seconds, then timeout will win, then enter catch and report “picture request timeout” message. The result is as follows:
Okay, I’m sure you get the idea of usage, so let’s hand-write a promise of our own!
III. Implementing a Promise of Your Own Based on PromiseA+
Step 1: Implement success and failure callback methods
To realize the functionality in the above code, which is the most basic functionality of promise. First of all, you need to create a constructor promise, create a promisel class, in the use of the time passed an executor executor, executor will pass two parameters: success (resolve) and failure (reject). As I said before, as long as it succeeds, it won’t fail, and as long as it fails, it won’t succeed. So, by default, when the call succeeds, it returns the success state, and when the call fails, it returns the failure state. The code is as follows:
class Promise {
constructor (executor){
this.status = 'panding';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (data) => {
if(this.status === 'pending'){
this.value = data;
this.status = "resolved";
this.onResolvedCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if(this.status === 'pending'){
this.reason = reason;
this.status = 'rejected';
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try{
executor(resolve,reject);
}catch (e){
reject(e);
}
}
The promise A+ specification states that in the presence of an exception error, the failure function is executed.
constructor (executor){
...... try{
executor(resolve,reject);
}catch(e){
reject(e);
}
}
Step 2: then method chaining call
The then method is the most basic method of Promise, which returns two callbacks, a success callback and a failure callback, implemented as follows:
then(onFulFilled, onRejected) {
if (this.status === 'resolved') {
onFulFilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
}
let p = new Promise(function(){
resolve('success');
})
p.then((data) => {console.log(data);},(err) => {});
p.then((data) => {console.log(data);},(err) => {});
p.then((data) => {console.log(data);},(err) => {});
The returned result is:
In order to achieve such an effect, the last code will be rewritten, we can call resolve each time the results into an array, each call reject the results into an array. That’s why two arrays are defined above, and the reason for traversing both arrays in resolve() and reject() respectively. So, before calling resolve() or reject(), when we are in the pending state, we will store the result of multiple then into an array, then the code above will change to:
then(onFulFilled, onRejected) {
if (this.status === 'resolved') {
onFulFilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
if (this.status === 'pending') {
this.onResolvedCallbacks.push(() => {
onFulFilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
}
}
The Promise A+ specification states that then methods can be chained together
In promise, to realize that the chain call returns a new promise, the result returned in the first then, either success or failure, will be returned to the success state in the next then, but if an exception error is thrown in the first then, it will be returned to the failure state in the next then.
When the chained call succeeds
A successful chain call returns a value, in a variety of cases, and based on the examples given, a rough list of possible outcomes. Therefore a separate method is written for the value returned by the chain call. Method passed four parameters, respectively, p2,x,resolve,reject, p2 refers to the last return to the promise, x said to run the results returned by the promise, resolve and reject is the method of p2. Then the code is written as:
function resolvePromise(p2,x,resolve,reject){
....
}
- The return result cannot be itself
var p = new Promise((resovle,reject) => {
return p;
})
Never succeeds or fails when the return result is itself, so an error should be thrown when returning itself
function resolvePromise(p2,x,resolve,reject){
if(px===x){
return reject(new TypeError('I'm quoting myself.'));
}
....
}
- The return result may be a promise
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new TypeError('circular reference'));
};
if(x !=null && (typeof x === 'object' || typeof x === 'function')){
let called;
try{
let then = x.then;
if(typeof then === 'function'){
then.call(x,y => {
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},r => {
if(called) return;
called = true;
reject(r);
});
}else{
resolve(x);
}
}catch (e){
if(called) return;
called = true;
reject(e)
}
}else{
resolve(x)
}
}
If the result is a common value, then resolve(x).
- Promise can only be invoked once for success or failure.
That is, when the call succeeds, you can’t call the failure again, and if both are called, whichever one is called first will be executed. The code is the same as above
Personally, I think this place is rather roundabout and needs to be sorted out slowly, step by step.
According to the principles of the promise A+ specification, promise encapsulates a series of built-in methods in its own framework.
- Methods for catching errors catch()
- Parsing all methods all()
- race race()
- Generate a successful promise resolve()
- Generate a failed promise reject()
Finally, I am attaching the full source code for you to peruse.
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new TypeError('circular reference'));
};
if(x !=null && (typeof x === 'object' || typeof x === 'function')){
let called;
try{
let then = x.then;
if(typeof then === 'function'){
then.call(x,y => {
if(called) return;
called = true;
resolvePromise(promise2,y,resolve,reject)
},r => {
if(called) return;
called = true;
reject(r);
});
}else{
resolve(x);
}
}catch (e){
if(called) return;
called = true;
reject(e)
}
}else{
resolve(x)
}
}
class Promise {
constructor (executor){
this.status = 'panding';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = (data) => {
if(this.status === 'pending'){
this.value = data;
this.status = "resolved";
this.onResolvedCallbacks.forEach(fn => fn());
}
}
let reject = (reason) => {
if(this.status === 'pending'){
this.reason = reason;
this.status = 'rejected';
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try{
executor(resolve,reject);
}catch (e){
reject(e);
}
}
then(onFuiFilled,onRejected){
onFuiFilled = typeof onFuiFilled === 'function' ? onFuiFilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected :err => {throw err;}
let promise2;
if(this.status === 'resolved'){
promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
try{
let x = onFuiFilled(this.value);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
},0)
});
}
if(this.status === 'rejected'){
promise2 = new Promise((resolve,reject) => {
setTimeout(() => {
try{
let x = onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
},0)
});
}
if(this.status === 'pending'){
promise2 = new Promise((resolve,reject) => {
this.onResolvedCallbacks.push( () =>{
setTimeout(() => {
try{
let x = onFuiFilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
},0)
});
this.onRejectedCallbacks.push( ()=>{
setTimeout(() => {
try{
let x = onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
})
}
return promise2;
}
catch (onRejected) {
return this.then(null, onRejected);
}
}
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let arr = [];
let i = 0;
function processData(index, data) {
arr[index] = data;
if (++i === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (data) {
processData(i, data)
}, reject)
}
})
}
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
})
}
Promise.resolve = function(value){
return new Promise((resolve,reject) => resolve(value);
}
Promise.reject = function(reason){
return new Promise((resolve,reject) => reject(reason));
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise( (resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd
}
module.exports = Promise;