github
blog
Promise
The statement of
When we use a Promise, we usually use new Promise((resolve, reject) => {}).
Thus we can see:
Promise
Is a class;Promise
The first argument to the constructor of a class is a function called the handler function (executor function
);- In the handler function, we take two parameters:
resolve
andreject
- When the asynchronous task completes successfully and returns a result value, we call
resolve
Functions; - Is called when an asynchronous task fails and returns the reason for the failure, usually an error object
reject
Function.
- When the asynchronous task completes successfully and returns a result value, we call
Therefore, we can tentatively declare the Promise class.
class Promise {
/** * constructor *@returns {Promise<object>}
* @param Executor <function>: Executor takes two arguments: resolve and reject */
constructor(executor) {
/ / the resolve to succeed
const resolve = () = > {};
/ / reject failure
const reject = () = > {};
/ / execution executorexecutor(resolve,reject); }}Copy the code
Implement the base state of the Promise
There are three states of Promise: pending, fulfilled and rejected:
Promise
The initial state of PI is PIpending
State;pending
The state can be converted tofulfilled
State andrejected
State;fulfilled
States cannot be changed to other states and must have an immutable value.rejected
States cannot be changed to other states, and there must be an immutable reason for the change.- When called in a handler function
resolve
Function and pass the parameter value, the state changes tofulfilled
, and can not be changed; - When called in a handler function
reject
Function and pass the argument reason, the state changes torejected
, and can not be changed; - If an error occurs during the execution of the processor function, the function is executed directly
reject
Function.
Therefore, we need to set three variables in the Promise class: state, value, and Reason, and then change the state value when the resolve, reject, and executor functions fail.
class Promise {
constructor(executor) {
// Initialize the state
this.state = 'pending';
// The value of success
this.value = undefined;
// Cause of failure
this.reason = undefined;
/** * resolve ()@param Value <any>: Successful value */
const resolve = (value) = > {
// Execute only when the state is pending
if(this.state === 'pending') {This is fulfilled gradually. This is fulfilled gradually
this.state = 'fulfilled';
/ / store the value
this.value = value; }};/** * reject failure function *@param Reason <any>: Indicates the failure cause */
const reject = (reason) = > {
// Execute only when the state is pending
if(this.state === 'pending') {// After the call to resolve, state is converted to rejected
this.state = 'rejected';
/ / store "reason
this.reason = reason; }};Reject () reject(); // Reject ()
try {
executor(resolve,reject);
}catch(e){ reject(e); }}}Copy the code
then
methods
A Promise has a THEN method with two parameters: onFulfilled and onRejected:
- Both arguments are a function that returns a result value;
- When the state of
fulfilled
Only performonFulfilled
And the incomingthis.value
; - When the state of
rejected
Only performonRejected
And the incomingthis.reason
;
So we can implement the then method.
class Promise {
constructor(executor){... }/** * then method *@param OnFulfilled <function>: * is called when the state is fulfilled@param OnRejected <function>: call */ when the state is rejected
then(onFulfilled, onRejected) {
This is very depressing. This is very depressing. This is very depressing
if(this.state === 'fulfilled') {/** * onFulfilled *@param Value <function>: result of success */
onFulfilled(this.value)
}
// If the state is rejected, onRejected and this.reason is passed
if(this.state === 'rejected') {/** * onRejected method *@param Reason <function>: Failure reason */
onRejected(this.reason)
}
}
}
Copy the code
Asynchronous implementation
A Promise is actually an asynchronous operation:
resolve()
Is in thesetTimeout
Executed within;- When you perform
then()
Delta delta delta, if the state is delta delta deltapending
We need to wait for the state to end before continuing, so at this point we need tothen()
Two parameters ofonFulfilled
andonRejected
Saved; - Because a
Promise
Instances can be called multiple timesthen()
, so we need toonFulfilled
andonRejected
All kinds of things are stored in arrays.
So we can improve the code by:
class Promise {
/** * constructor *@returns {Promise<object>}
* @param Executor <function>: Executor takes two arguments: resolve and reject */
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// Store an array of onFulfilled
this.onResolvedCallbacks = [];
// Store an array of onRejected
this.onRejectedCallbacks = [];
const resolve = (value) = > {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
// Once resolve is executed, call the onResolvedCallbacks function
this.onResolvedCallbacks.forEach(fn= >fn()); }};const reject = (reason) = > {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
// Once reject is executed, call the onRejectedCallbacks array function
this.onRejectedCallbacks.forEach(fn= >fn()); }};try {
executor(resolve, reject);
} catch(e) { reject(e); }}then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
if (this.state === 'rejected') {
onRejected(this.reason)
}
This will be fulfilled, this will be fulfilled, this will be fulfilled, and this will be fulfilled
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() = > {
onFulfilled(this.value)
})
this.onRejectedCallbacks.push(() = > {
onRejected(this.reason)
})
}
}
}
Copy the code
Implement chained calls
We often use a Promise as follows:
new Promise()
.then()
.then()
.then()
Copy the code
This method is called chained calls and is usually used to solve Callback Hell, as shown in this code:
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ':' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this()}})}})Copy the code
To implement chained calls, we need to meet the following requirements:
- We need to be able to
then()
Return a newPromise
Instance; - If the last one
then()
Returns a value, then the value isonFulfilled()
oronRejected()
We need to pass this value to the next onethen()
In the.
For the return value of the previous then(), we need to align for certain processing, so encapsulate a resolvePromise() method for judgment processing;
Next we modify the then() method:
class Promise {
constructor(executor){... }/** * then method *@returns {Promise<object>}
* @param OnFulfilled <function>: * is called when the state is fulfilled@param OnRejected <function>: call */ when the state is rejected
then(onFulfilled, onRejected) {
// Return a new Promise instance
const newPromise = new Promise((resolve, reject) = > {
if (this.state === 'fulfilled') {
const x = onFulfilled(this.value)
// The return value is processed
resolvePromise(newPromise, x, resolve, reject);
}
if (this.state === 'rejected') {
const x = onRejected(this.reason);
// The return value is processed
resolvePromise(x, resolve, reject);
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() = > {
const x = onFulfilled(this.value);
// The return value is processed
resolvePromise(newPromise, x, resolve, reject);
})
this.onRejectedCallbacks.push(() = > {
const x = onRejected(this.reason);
// The return value is processedresolvePromise(newPromise, x, resolve, reject); }}}));returnnewPromise; }}function resolvePromise() {}
Copy the code
completeresolvePromise
function
For the return value from then(), we store it as an x variable, and then we need to do something about it:
- judge
x
Isn’t itPromise
Instance;- If it is
Promise
Instance, takes its result as the newPromise
The successful result of the instance; - If it’s a normal value, it’s just
Promise
A successful outcome;
- If it is
After we process the return value, we need to return the result using newPromise’s resolve and reject methods.
The other thing to note here is that if x is equal to newPromise, it will cause a circular reference, resulting in an infinite loop.
let p = new Promise(resolve= > {
resolve(0);
});
const p2 = p.then(data= > {
// Loop references, and wait for their own completion, resulting in an infinite loop
return p2;
})
Copy the code
Therefore, the resolvePromise function takes four arguments, newPromise, X, resolve, and reject.
So let’s implement the resolvePromise function:
/** * resolvePromise method *@param NewPromise <object>: newPromise instance *@param X <any>: the return value of the previous THEN () *@param Resolve <function> : The resolve method * for the Promise instance@param Reject <function> : Reject method */ for the Promise instance
function resolvePromise(newPromise, x, resolve, reject) {
// Circular reference error
if(x === newPromise){
/ / reject an error
return reject(new TypeError('Chaining cycle detected for promise'));
}
// Prevent multiple calls
let called;
if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
// x is a Promise instance
if (typeof then === 'function') {
// Use call to execute then(). The first argument to call is this, followed by arguments to then(), i.e. the second is a successful callback method, and the third is a failed callback function
then.call(x, y= > {
// Only one success or failure call can be made
if(called)return;
called = true;
// The result of resolve is still a Promise instance, so continue parsing
resolvePromise(newPromise, y, resolve, reject);
}, err= > {
// Only one success or failure call can be made
if(called)return;
called = true;
// Reject is rejected on failurereject(err); })}else {
// x is an ordinary object or method and returns it directlyresolve(x); }}catch (e) {
if(called)return;
called = true; reject(e); }}else {
// if x is an ordinary value, return it directlyresolve(x); }}Copy the code
onFulfilled
andonRejected
This is a pity pity. This is a pity pity. This is a pity pity.
- They’re all optional arguments, and they’re all functions, and if they’re not functions, they’re ignored;
- if
onFulfilled
It’s not a function, it’s just a functionvalue => value
; - if
onRejected
It’s not a function, it’s just a functionerr => {throw err}
;
- if
class Promise {
constructor(executor){... }then(onFulfilled, onRejected) {
This function will be fulfilled fulfilled. If this function is not fulfilled, omit onFulfilled and return value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
// If onRejected is not a function, throw an error without onRejected
onRejected = typeof onRejected === 'function' ? onRejected : err= > { throwerr }; . }}Copy the code
Secondly, onFulfilled and onRejected cannot be called synchronously. They must be called asynchronously. So let’s use setTimeout to solve the one-step problem.
class Promise {
constructor(executor){... }then(onFulfilled, onRejected) {
This function will be fulfilled fulfilled. If this function is not fulfilled, omit onFulfilled and return value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
// If onRejected is not a function, throw an error without onRejected
onRejected = typeof onRejected === 'function' ? onRejected : err= > {
throw err
};
return new Promise((resolve, reject) = > {
if (this.state === 'fulfilled') {
// Asynchronous invocation
setTimeout(() = > {
try {
const x = onFulfilled(this.value)
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
}
if (this.state === 'rejected') {
// Asynchronous invocation
setTimeout(() = > {
try{
const x = onRejected(this.reason);
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
}
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() = > {
// Asynchronous invocation
setTimeout(() = > {
try {
const x = onFulfilled(this.value);
resolvePromise(x, resolve, reject);
}catch (e){
reject(e)
}
})
})
this.onRejectedCallbacks.push(() = > {
// Asynchronous invocation
setTimeout(() = > {
try {
const x = onRejected(this.reason);
resolvePromise(x, resolve, reject);
}catch(e){ reject(e) } }) }) } }); }}Copy the code
implementationPromise
Other methods of
Promise.all()
The promise.all () method receives an iterable input of a Promise, including Array, Map, and Set. A Promise instance is then returned, and the result of the instance callback is an array containing the callback results of all the promises entered.
But as soon as the reject callback executes on any incoming promise or an invalid promise is entered, an error is immediately thrown.
/** * promise. all@returns {Promise<object>}
* @param Promises <iterable>: promises iterable input */
Promise.all = function (promises) {
let arr = [];
return new Promise((resolve, reject) = > {
if(! promises.length) resolve([]);/ / traverse promises
for(const promise of promises) {
promise.then(res= > {
arr.push(res);
if(arr.length === promises.length){
resolve(arr);
}
}, reject)
}
})
}
Copy the code
Promise.allSettled()
Promise.allsettled () is similar to promise.all () in that it receives an iterable of a Promise, but returns a given Promise with an array of objects. Each object identifies the corresponding PROMISE result.
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) = > setTimeout(reject, 100.'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) = > console.log(results));
// > Array [Object { status: "fulfilled", value: 3 }, Object { status: "rejected", reason: "foo" }]
Copy the code
Implementation:
/** * promise.allsettled method *@returns {Promise<object>}
* @param Promises <iterable>: promises iterable input */
Promise.allSettled = function (promises) {
let arr = [];
return new Promise((resolve, reject) = > {
try {
const processData = (data) = > {
arr.push(data);
if(arr.length === promises.length){ resolve(arr); }}if(! promises.length) resolve([]);/ / traverse promises
for(const promise of promises) {
promise.then(res= > {
processData({state:'fulfilled'.value: res})
}, err= > {
processData({state:'rejected'.reason: err})
})
}
}catch (e){
reject(e)
}
})
}
Copy the code
Promise.any()
Promise.any() accepts the iterable input of a Promise, just like promise.all () and promise.allsettled (). If one of the promises succeeds, the promise that was already successful is returned, but if none succeeds, a failed promise is returned.
/** * promise.any method *@returns {Promise<object>}
* @param Promises <iterable>: promises iterable input */
Promise.any = function (promises) {
return new Promise((resolve, reject) = > {
// If the argument passed is an empty iterable, return a Promise already rejected
if(! promises.length) reject();// If the passed argument does not contain any promises, return an asynchronously resolved promise.
if (typeof promises[Symbol.iterator] ! = ='function' ||
promises === null ||
typeof promises === 'string') {
resolve()
}
let i = 0;
/ / traverse promises
for (const promise of promises) {
promise.then(res= > {
i++;
resolve(res);
}, err= > {
i++;
if(i === promises.length) { reject(err); }})}})Copy the code
Promise.race()
Promise.race(), again, is an iterable input that receives a Promise. Once a promise in the iterator completes, whether it succeeds or fails, the promise is returned.
/** * promise.race method *@returns {Promise<object>}
* @param Promises <iterable>: promises iterable input */
Promise.race = function (promises) {
return new Promise((resolve, reject) = > {
for (const promise of promises) {
promise.then(resolve, reject)
}
})
}
Copy the code
Promise.reject()
andPromise.resolve()
The promise.reject () method returns a Promise object with a reason for rejecting it; The promise.resolve () method returns a Promise object resolved at a fixed value.
/** * Promise. Reject@returns {Promise<object>}
* @param val<any>
*/
Promise.reject = function (val) {
return new Promise(reject= > reject(val))
}
/** * promise. resolve method *@returns {Promise<object>}
* @param val<any>
*/
Promise.resolve = function (val) {
return new Promise(resolve= > resolve(val))
}
Copy the code
catch()
andfinally()
The catch() method is used to handle failure by passing a handler and returning a Promise instance. It is actually the syntactic sugar of then() and only accepts data in the rejected state.
Finally () executes the specified callback function at the end of the promise, whether the result is fufilled or Rejected. It also returns a Promise instance.
class Promise {
constructor(executor){... }then(onFulfilled, onRejected){... }/** * catch method *@returns {Promise<object>}
* @param Callback <function>: processing function */
catch(callback) {
return this.then(null, callback);
}
/** finally method *@returns {Promise<object>}
* @param Callback <function>: processing function */
finally(callback) {
return this.then(res= > {
return Promise.resolve(callback()).then(() = > res)
}, err= > {
return Promise.reject(callback()).then(() = > {
throw err
})
})
}
}
Copy the code
Promise/A + testing
Promise/A+ specification: github.com/promises-ap…
Promise/A+ test tool: github.com/promises-ap…
Install the Promises – Aplus-tests plugin.
yarn add promises-aplus-tests
Copy the code
Insert the following code after promise.js.
/ / test
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
Then enter the command line to test.
promises-aplus-tests Promise.js
Copy the code
Results:
872 passing (18s)
Copy the code