What problem did Promise solve
Before promises, multiple asynchronous operations with nested relationships tended to look like this: layer upon layer of callbacks (callback hell) to satisfy that nesting relationship. Real-world code must have a lot of logic before each asynchronous operation, and as the amount of code increases, it becomes less and less maintainable.
const fs = require('fs');
fs.readFile('./a.txt'.'utf8'.(err, data) = > {
// ...
fs.readFile(data, 'utf8'.(err, data) = > {
// ...
fs.readFile(data, 'utf8'.(err, data) = > {
console.log(data);
// ...})})})Copy the code
Logical relation:
With Promise in place, we can rewrite the above code like this:
const readFIlePromise = filename= > {
return new Promise((resolve, reject) = > {
fs.readFile(filename, 'utf8'.(err, data) = > {
if (err) reject(err);
resolve(data);
})
})
}
readFIlePromise('./a.txt')
.then(data= > {
// ...
return readFIlePromise(data);
})
.then(data= > {
// ...
return readFIlePromise(data);
})
.then(data= > {
console.log(data);
// ...
})
.catch(err= > {
console.error(err);
})
Copy the code
Logical relation:
Look,Promise
Since the advent of the chain-call script overwriting the nested script is not much clearer
How to fulfill a Promise
Many Promise libraries, such as Bluebird and ES6-PROMISE, are based on the Promise A+ specification. The most obvious benefit of implementing the Promise A+ specification is that you can call each other. So study Promise, still suggest read A+ specification again.
Promise A + specification
Let’s go back to Promise
Let’s take a look back at Promise. You need to know how to use it before you know how to write it right. Look at the code:
const p1 = new Promise((resolve, reject) = > {
console.log('CREATE p1');
resolve('p1');
});
const p2 = p1.then(data= > {
console.log('p1-data', data);
throw new Error('Wrong');
});
const p3 = p2.then(data= > {
console.log('p2-data', data);
}).catch(err= > {
console.log('p2-error', err);
})
// create p1
// p1-data p1
// p2-error error: error
Copy the code
Just from the code level above, we can seePromise
It has the following characteristics:
- The process of instantiation needs to receive a function (temporarily called
executor
) will be executed internally immediatelyexecutor
This function; executor
Execute it if it succeedsresolve
And pass on the success data. Failure/error is executedreject
, sends an error message;
In combination withPromise A+
Norms, we can concludePromise
Features:
Promise
There are only three states:pending
,fulfilled
,rejected
;
new Promise
You need to pass in an executorexecutor
.executor
Will be executed immediately;executor
Receives two parameters, respectivelyresolve
,reject
;- The default is
pending
.pending
The state can be converted tofulfilled
The state orrejected
State, once the state is changed, it cannot be changed;
fulfilled
There must be one statevalue
To save successful results;
rejected
There must be one statereason
To save the result of failure;
Promise
You have to have onethen
Method,then
Method receives two parameters, one for a successful callbackonFulfilled
, a callback after a failureonRejected
;
Promise
If successful, the command is executedthen
Method successfully callbackonFulfilled
And,value
Will be used as an argument for a successful callback;Promise
If it fails, it will be executedthen
Method failure callbackonRejected
And,reason
Will be used as an argument for a failed callback;
Tips:
It’s normal to be confused by the words. I was confused, too. We can see the following implementation directly, according to the implementation and then compare the Promise A+ specification is easier to understand:
1. Implement a base version firstPromise
Based on the above features, let’s take our time and implement a basic version of Promise:
// Define three states: Pending, depressing, and Rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
/** * actuator *@param {Function} executor
*/
constructor(executor) {
this.status = PENDING; // The initialization is pending
this.value = undefined; // Save successful data
this.reason = undefined; // Cause of save failure
// Execute this function in success state
const resolve = data= > {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = data; }}// Failed to execute this function
const reject = reason= > {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason; }}try {
// Execute executor immediately when new
executor(resolve, reject);
} catch(err) {
We also throw reject for errors thrown by the executorreject(err); }}then(onFulfilled, onRejected) {
if(this.status === FULFILLED) {
onFulfilled(this.value);
};
if(this.status === REJECTED) {
onRejected(this.reason); }}}Copy the code
testDemo 1
new Promise((resolve, reject) = > {
resolve(1);
})
.then(data= > {
console.log('success', data);
}, err= > {
console.log('err', err);
})
// success 1
Copy the code
The base version of Promise is complete, but it only supports the most basic functionality – passing normal values (success/failure).
testDemo 2
new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve(1);
}, 1000)
})
.then(data= > {
console.log('success', data);
}, err= > {
console.log('err', err);
})
// Do not print anything
Copy the code
Demo 2 prints nothing because the executor function passed in by New Promise takes 1 second to become successful, whereas the then method executes immediately after the executor synchronization code completes (the Promise state is still pending, So neither of the then callbacks will be executed.
2. Upgrade the base versionPromise
// Define three states: Pending, depressing, and Rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
/** * actuator *@param {Function} executor
*/
constructor(executor) {
this.status = PENDING; // The initialization is pending
this.value = undefined; // Save successful data
this.reason = undefined; // Cause of save failure
// +
this.onFulfilledCallbacks = [] // Save the successful callback queue
this.onRejectedCallbacks = [] // Save the failed callback queue
// Execute this function in success state
const resolve = data= > {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = data;
// +
this.onFulfilledCallbacks.forEach(cb= >cb()); }}// Failed to execute this function
const reject = reason= > {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// +
this.onRejectedCallbacks.forEach(cb= >cb()); }}try {
// Execute executor immediately when new
executor(resolve, reject);
} catch(err) {
We also throw reject for errors thrown by the executorreject(err); }}then(onFulfilled, onRejected) {
if(this.status === FULFILLED) {
onFulfilled(this.value);
};
if(this.status === REJECTED) {
onRejected(this.reason);
}
// +
if(this.status === PENDING) {
this.onFulfilledCallbacks.push(() = > {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() = > {
onRejected(this.reason); })}}}Copy the code
testDemo 3
new Promise((resolve, reject) = > {
setTimeout(() = > {
reject(2);
}, 1000)
})
.then(data= > {
console.log('success', data);
}, err= > {
console.log('err', err);
})
// After 1s, err 2 is printed
Copy the code
3. ToPromise
Plus value penetration and chain calls
new Promise((resolve, reject) = > {
resolve(3);
})
.then()
.then()
.then(data= > {
console.log('success', data);
}, err= > {
console.log('err', err);
})
Copy the code
The Promise we currently implement does not support this form of invocation (value penetration). We also need to improve our Promise according to A + specifications 2.2 and 2.3. So it is recommended to look at the following implementation process for the A + specification:
test.js
// Define three states: Pending, depressing, and Rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
/ / 2.3
const resolvePromise = (promise, x, resolve, reject) = > {
2.3.1 Reject if promise and X are the same, reject because of a type error
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
2.3.3.3.3 Avoid multiple calls. Resolve /reject will not be executed after status changes
let called = false;
2.3.3 If x is an object/function
if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
try {
// 2.3.3.1 Define a then variable to save x. teng
const then = x.then;
// 2.3.3.3 If then is a function
if (typeof then === 'function') {
// 2.3.3.3 then is called with x as this. The first argument is a callback to the success state and the second argument is a callback to the failure state
then.call(x, y= > {
if(called) return;
called = true;
// 2.3.3.3.1 If the callback succeeds, proceed with resolvePromise
resolvePromise(promise, y, resolve, reject);
}, r= > {
if(called) return;
called = true;
2.3.3.3.2 Execute reject if the callback is in the failed state
reject(r);
});
} else {
// 2.3.3.4 If then is not a function, resolve, xresolve(x); }}catch(e) {
if(called) return;
called = true;
// 2.3.3.2 Reject is rejected when an error occurs when the attribute x. Chen is obtained. The error is a catch errorreject(e); }}else {
// 2.3.4 Resolve if x is not an object/functionresolve(x); }}class Promise {
/** * actuator *@param {Function} executor
*/
constructor(executor) {
this.status = PENDING; // The initialization is pending
this.value = undefined; // Save successful data
this.reason = undefined; // Cause of save failure
this.onFulfilledCallbacks = [] // Save the successful callback queue
this.onRejectedCallbacks = [] // Save the failed callback queue
// Execute this function in success state
const resolve = data= > {
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = data;
this.onFulfilledCallbacks.forEach(cb= >cb()); }}// Failed to execute this function
const reject = reason= > {
if(this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(cb= >cb()); }}try {
// Execute executor immediately when new
executor(resolve, reject);
} catch (err) {
We also throw reject for errors thrown by the executorreject(err); }}/ / 2.2
then (onFulfilled, onRejected) {
/ / value through
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };
let promise = new Promise((resolve, reject) = > {
if (this.status === FULFILLED) {
// this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
// 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caughtreject(e); }},0);
};
if (this.status === REJECTED) {
// this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
setTimeout(() = > {
try {
const x = onRejected(this.reason);
// 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caught
reject(e)
}
}, 0);
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() = > {
// this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
// 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caughtreject(e); }},0);
});
this.onRejectedCallbacks.push(() = > {
// this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
setTimeout(() = > {
try {
const x = onRejected(this.reason);
// 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
resolvePromise(promise, x, resolve, reject);
} catch (e) {
// 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caught
reject(e)
}
}, 0); }}}));The 2.2.7 then method must return a promise
returnpromise; }}Copy the code
After writing, we need to verify our Promise using the test script provided by Promise A+.
- Install the test script:
npm install -g promises-aplus-tests
; - Add the following code at the end of the test file and export it:
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve,reject) = >{
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}
module.exports = Promise;
Copy the code
- Finally, execute the test command:
promises-aplus-tests test.js
; - Test results:
Realize the Promise. The prototype. The catch ()
The catch method is actually implemented with the help of the THEN method, which is nothing more than a successful callback to null.
Promise.prototype.catch = function (errCallBack){
return this.then(null, errCallBack)
}
// test
new Promise((resolve, reject) = > {
reject(2);
})
.then(data= > {
console.log('success', data);
})
.catch(err= > {
console.log('catch', err); // catch 2
})
Copy the code
Realize the Promise. The resolve ()
Promise. Resolve is to produce a successful Promise.
Promise.resolve = function(data) {
return new Promise((resolve, reject) = >{ resolve(data); })}// test
Promise.resolve(new Promise((resolve, reject) = > {
resolve('ok');
})).then(data= >{
console.log(data,'success') // ok success
}).catch(err= >{
console.log(err,'error')})Copy the code
Promise.resolve also requires the wait function. That is, if data is a promise, it needs to complete. Rewrite constructor’s resolve method:
// Execute this function in success state
const resolve = data= > {
// New: If data is a Promise, parse it recursively
if (data instanceof Promise) {
return data.then(resolve, reject);
}
this.status = FULFILLED;
this.value = data;
this.onFulfilledCallbacks.forEach(cb= > cb());
}
// test
Promise.resolve(new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('ok');
}, 3000);
})).then(data= >{
console.log(data,'success') // Print after 3 seconds: OK success
}).catch(err= >{
console.log(err,'error')})Copy the code
Thus, promise.resolve has wait capability.
Realize the Promise. Reject ()
This method returns a failed promise and simply throws the error result as reason.
Promise.reject = function(reason) {
return new Promise((resolve, reject) = >{ reject(reason); })}// test
Promise.reject(2).then(data= >{
console.log(data,'success')
}).catch(err= >{
console.log(err,'error') // 2 error
})
Copy the code
Realize the Promise. Prototype. Finally ()
This method indicates that it will be executed regardless of success/failure (not last). If the last successful result is returned, finally returns the last successful result after executing the internal code. If the last promise failed, the reason for the failure is thrown.
Promise.prototype.finally = function(callBack) {
return this.then(data= > {
return Promise.resolve(callBack()).then(() = > data);
}, reason= > {
return Promise.resolve(callBack()).then(() = > {
throwreason; })})}// test
Promise.resolve(1).finally(() = >{
return new Promise((resolve,reject) = >{
setTimeout(() = > {
resolve(2)},2000);
})
}).then(data= >{
console.log(data,'success') // Prints after 2 seconds: 1 success
}).catch(err= >{
console.log(err,'error')})Copy the code
Realize the Promise. Race ()
This method is used to process multiple requests and returns the result of which request is completed first.
Promise.race = function(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
for (let i = 0, len = promiseList.length; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(resolve, reject);
} else { / / common valuesresolve(val); }}})}// test
let p1 = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('ok1');
}, 3000);
})
let p2 = new Promise((resolve, reject) = > {
setTimeout(() = > {
reject('ok2');
}, 2000);
})
Promise.race([1.2.3, p1, p2]).then(data= > {
console.log('success', data);
}, err= > {
console.log('error', err);
})
// success 1
Copy the code
Implement Pomise. All ()
This method is used to process multiple requests, and if multiple requests are successful, it returns an array of the results of each request’s success. If there is only one failure, there is a failure.
Promise.all = function(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
const resultArr = [];
const len = promiseList.length;
let currentIndex = 0;
const getResult = (key, val) = > {
resultArr[key] = val;
if(++currentIndex === len) { resolve(resultArr); }}for (let i = 0; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(data= > {
getResult(i, data);
}, reject);
} else { / / common valuesgetResult(i, val); }}})}// test
let p1 = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('ok1');
}, 1000);
})
let p2 = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('ok2');
}, 2000);
})
Promise.all([1.2.3,p1,p2]).then(data= > {
console.log('success', data);
}, err= > {
console.log('error', err);
})
// Print after 2 seconds: SUCCESS [1, 2, 3, 'ok1', 'ok2']
Copy the code
Realize the Promise. AllSettled ()
The method waits until all parameter instances return a result, reject or resolve. Finally, an array is returned, each containing two attributes, status and value.
Promise.allSettled = function(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
const resultArr = [];
const len = promiseList.length;
let currentIndex = 0;
const getResult = (key, val, status) = > {
resultArr[key] = {
status: status,
};
resultArr[key].status === 'fulfilled' ? resultArr[key].value = val : resultArr[key].reason = val;
if(++currentIndex === len) { resolve(resultArr); }}for(let i = 0; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(data= > {
getResult(i, data, 'fulfilled');
}, reason= > {
getResult(i, reason, 'rejected'); })}else {
getResult(i, val, 'fulfilled'); }}})}// test
const promise1 = Promise.resolve('ok1');
const promise2 = Promise.reject('err1');
const promise3 = new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve('ok2');
}, 1000);
})
const allSettledPromise = Promise.allSettled([promise1, promise2, promise3]);
allSettledPromise.then(function (results) {
console.log(results);
});
// Output after 1 second
/* [ { status: 'fulfilled', value: 'ok1' }, { status: 'rejected', reason: 'err1' }, { status: 'fulfilled', value: 'ok2' } ] */
Copy the code
Realize the Promise. Any ()
This method is the opposite to the depressing state of Promise. All. As long as one parameter instance becomes the depressing state, the packaging instance will become the depressing state. If all parameter instances become the Rejected state, the wrapper instance becomes the Rejected state.
Promise.any = function(promiseList) {
if (!Array.isArray(promiseList)) {
throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
const resultArr = [];
const len = promiseList.length;
let currentIndex = 0;
const getResult = (key, val) = > {
resultArr[key] = val;
if(++currentIndex === len) { reject(resultArr); }}for (let i = 0; i < len; i++) {
const val = promiseList[i];
if (val && typeof val.then === 'function') {
val.then(resolve, reason= > {
getResult(i, reason);
});
} else { / / common valuesresolve(val); }}})}// test
const promise1 = Promise.reject('err1');
const promise2 = Promise.reject('err2');
const promise3 = new Promise((resolve, reject) = > {
setTimeout(() = > {
reject('err3');
}, 1000);
})
const allSettledPromise = Promise.any([promise1, promise2, promise3]);
allSettledPromise
.then(data= > {
console.log('resolve', data);
})
.catch(reason= > {
console.log('reject', reason);
})
Reject ['err1', 'err2', 'err3']
Copy the code
At this point,ES Promise
The methods are all implemented. Once you’ve done it yourself, yeahPromise
,then
A better understanding of the chain calls and value penetration of
Refer to the link
- ES6 – Promise use tutorial
- Promise A + specification
- 20 lines fulfill a Promise
- Promise the interview questions