preface
There are plenty of good articles on the Promise principle in nuggets. However, the author is always in the state of reading and writing, which is the purpose of the author to write this article. In order to clarify the writing ideas of Promise, I wrote a wave of code from scratch, and it is also convenient for myself to review in the future.
The role of the Promise
Promise is a popular solution for asynchronous JavaScript programming. It appears to solve the problem of callback hell, so that users can write asynchronous code through the chain writing method. The author will not introduce the specific usage, you can refer to ruan Yifeng teacher ES6 Promise tutorial.
Knowledge before class
Observer model
What is the observer model:
The observer pattern defines a one-to-many dependency that allows multiple observer objects to listen to a target object at the same time. When the state of the target object changes, all observer objects are notified so that they can update automatically.
Promises are implemented based on the observer design pattern. The functions to be performed by the then function are stuffed into the observer array, and when the Promise state changes, all the functions in the observer array are executed.
Event loop mechanism
Implementing promises involves JavaScript’s EventLoop mechanism, as well as the concept of macro and microtasks.
The flow chart of the event loop mechanism is as follows:
Take a look at this code:
console.log(1);
setTimeout((a)= > {
console.log(2);
},0);
let a = new Promise((resolve) = > {
console.log(3);
resolve();
}).then((a)= > {
console.log(4);
}).then((a)= > {
console.log(5);
});
console.log(6);
Copy the code
If you can’t tell the output immediately, I suggest you check out the event loop. There are many good articles in nuggets.
Promises/A + specification
Promises/A+ is A community norm, and if you want to make A Promise, we need to follow this standard. Then we’ll refine our own promises based on the specification.
Promise core points
Before we start writing promises, let’s go over a few important points.
executor
// Create the Promise object x1
// And execute the business logic in executor functions
function executor(resolve, reject){
// The business logic processes the successful result
constvalue = ... ; resolve(value);// Failure result
// const reason = ... ;
// reject(reason);
}
let x1 = new Promise(executor);
Copy the code
First, a Promise is a class that accepts an executor function that takes two arguments, resolve and Reject. These are functions defined internally by the Promise that change the state and execute the corresponding callback.
Since the Promise itself does not know whether the result will fail or succeed, it simply provides a container for the asynchronous operation. In fact, the control is in the hands of the user, who can call the above two parameters to tell the Promise whether the result will succeed. Both resolve and Reject execute the callback by passing the result of the business logic processing (value/ Reason) as arguments.
Three state
Promise has three states:
pending
: waitresolved
That they have been successfulrejected
: has failed
There are only two possibilities for a Promise state to change: from Pending to Resolved or from Pending to Rejected, as shown below (from the Promise mini-book) :
And the thing to notice is that once the state changes, it doesn’t change any more, and it stays the same. This means that when we call resolve in the executor function, we have no effect when we call reject, and vice versa.
// And execute the business logic in executor functions
function executor(resolve, reject){
resolve(100);
// All calls to resolve, reject are invalid,
// This is resolved and will not change
reject(100);
}
let x1 = new Promise(executor);
Copy the code
then
Each promise has a then method, which is a callback that needs to be executed when a promise returns a result. This method takes two optional arguments:
onFulfilled
: successful callback;onRejected
: failed callback;
Below (from the Promise mini-book) :
// ...
let x1 = new Promise(executor);
// x1 deferred binding callback onResolve
function onResolved(value){
console.log(value);
}
// x1 delays binding the callback function onRejected
function onRejected(reason){
console.log(reason);
}
x1.then(onResolved, onRejected);
Copy the code
Write a Promise and outline the process
Here’s a quick overview of how to write a Promise:
Executor has three states
new Promise
, you need to pass oneexecutor
The executor function, in the constructor,The executor function executes immediatelyexecutor
The execution function takes two arguments, respectivelyresolve
和reject
Promise
Only frompending
到rejected
, or frompending
到fulfilled
Promise
Once confirmed, the state is frozen and does not change
Then method
- All of the
Promise
There arethen
Method,then
Receives two parameters, respectivelyPromise
Successful callbackonFulfilled
, and failed callbacksonRejected
- If the call
then
When,Promise
If yes, run the commandonFulfilled
And willPromise
Is passed as a parameter; ifPromise
If failed, executeonRejected
And willPromise
The cause of failure is passed in as a parameter; ifPromise
The state ispending
, you need toonFulfilled
和onRejected
Functions are stored, wait for the state to be determined, and then execute the corresponding functions (observer mode) then
The parameters of theonFulfilled
和onRejected
You don’t have to,Promise
Value penetration is possible.
Chain calls and processes the then return value
Promise
canthen
Many times,Promise
的then
Method returns a new onePromise
.- if
then
Returns a normal value, which would render the result (value
) as an argument, passed to the nextthen
Successful callback (onFulfilled
) - if
then
Throws an exception, then the exception (reason
) as an argument, passed to the nextthen
Failed callback (onRejected
) - if
then
It returns onepromise
Or otherthenable
Object, so you have to wait for thispromise
When the execution is complete,promise
If it works, move on to the nextthen
The successful callback; If you fail, move on to the nextthen
The failed callback.
The above is the general implementation process, if you feel confused, it doesn’t matter, as long as you have a general impression, we will talk about one by one.
So let’s start with the simplest implementation.
First edition (start with a simple example)
Let’s start with a simple version that does not yet support state, chained calls, and only calls to a then method.
To a 🌰
let p1 = new MyPromise((resolve, reject) = > {
setTimeout((a)= > {
resolved('It worked');
}, 1000);
})
p1.then((data) = > {
console.log(data);
}, (err) => {
console.log(err);
})
Copy the code
The example is simple: return success after 1s and print then.
implementation
We define a MyPromise class, and we write code in it as follows:
class MyPromise {
// ts interface definition...
constructor (executor: executor) {
// Used to save the value of resolve
this.value = null;
// Saves the reject value
this.reason = null;
// Used to save successful callbacks to THEN
this.onFulfilled = null;
// Used to save failed callbacks for THEN
this.onRejected = null;
// Executor's resolve parameter
// Used to change the state and perform a successful callback in THEN
let resolve = value= > {
this.value = value;
this.onFulfilled && this.onFulfilled(this.value);
}
// Executor reject parameter
// Used to change state and perform failed callbacks in THEN
let reject = reason= > {
this.reason = reason;
this.onRejected && this.onRejected(this.reason);
}
// Execute the executor function
// Pass in the two functions we defined above as arguments
// It is possible to make an error while executing the executor function, so you need to try and catch it
try {
executor(resolve, reject);
} catch(err) { reject(err); }}// Define the then function
// And copy the parameters in then to this. onpity and this.onRejected
private then(onFulfilled, onRejected) {
this.onFulfilled = onFulfilled;
this.onRejected = onRejected; }}Copy the code
Ok, so we’re done with the first version, isn’t that easy?
Resolve is executed after the then callback is registered and then before the assignment callback is executed.
The above example is fine because resolve is packaged in setTimeout and will be executed by the next macro task, at which point the callback function is registered.
Try taking resolve out of setTimeout, and this will cause problems.
There is a problem
This version of the implementation is very simple, there are a few problems:
- The concept of states is not introduced
The concept of state was not introduced. Now the state can be changed at will, which does not conform to the rule that the Promise state can only change from the waiting state.
- Chain calls are not supported
Normally we would make a chain call to a Promise:
let p1 = new MyPromise((resolve, reject) = > {
setTimeout((a)= > {
resolved('It worked');
}, 1000);
})
p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
Copy the code
- Only one callback is supported, and if more than one callback exists, the last one overwrites the previous one
In this example, onResolved2 overrides onResolved1 and onRejected2 overrides onRejected1.
let p1 = new MyPromise((resolve, reject) = > {
setTimeout((a)= > {
resolved('It worked');
}, 1000);
})
// Register multiple callback functions
p1.then(onResolved1, onRejected1);
p1.then(onResolved2, onRejected2);
Copy the code
Let’s go one step further and solve these problems.
Version 2 (Implementing chain calls)
In this version, we introduce the concept of states and implement the function of chain calls.
Combined with state
Promise has three states: Pending, Resovled, and Rejected. You can only change the state from pending to Resovled or Rejected.
- We define a property
status
: Records the current informationPromise
The state of the - To prevent writing errors, we define the state as a constant
PENDING
,RESOLVED
,REJECTED
. - In the meantime we’re going to save
then
The success callback is defined as an array:this.resolvedQueues
与this.rejectedQueues
We can putthen
To solve the third problem we mentioned above.
class MyPromise {
private static PENDING = 'pending';
private static RESOLVED = 'resolved';
private static REJECTED = 'rejected';
constructor (executor: executor) {
this.status = MyPromise.PENDING;
// ...
// An array of successful callbacks for then
this.resolvedQueues = [];
// An array of failed callbacks for then
this.rejectedQueues = [];
let resolve = value= > {
// When the state is pending, change the state of the promise to success
// Pass in value while iterating over the function in the successful callback array
if (this.status == MyPromise.PENDING) {
this.value = value;
this.status = MyPromise.RESOLVED;
this.resolvedQueues.forEach(cb= > cb(this.value))
}
}
let reject = reason= > {
// When the state is pending, change the state of the promise to a failed state
// Pass reason through the function in the failed callback array
if (this.status == MyPromise.PENDING) {
this.reason = reason;
this.status = MyPromise.REJECTED;
this.rejectedQueues.forEach(cb= > cb(this.reason))
}
}
try {
executor(resolve, reject);
} catch(err) { reject(err); }}}Copy the code
Perfect then function
Next, we will improve the method in THEN. Before, we will directly assign the two parameters of THEN, onFulfilled and onRejected, to the instance attribute of Promise, which is used to save the successful and failed function callback.
Now we need to stuff these two attributes into two arrays: resolvedQueues and rejectedQueues.
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
// First check whether the two arguments are function types, since they are optional
When an argument is not a function type, you need to create a function to assign to the corresponding argument
// This implements pass-through
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
onRejected = typeof onRejected === 'function' ? onRejected : reason= > { throw reason}
// When the state is waiting, two parameters need to be inserted into the corresponding callback array
// Execute the function in the callback function after the state changes
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push(onFulfilled)
this.rejectedQueues.push(onRejected)
}
// The state is successful, so call ondepressing function directly
if (this.status === MyPromise.RESOLVED) {
onFulfilled(this.value)
}
// If the status is successful, call onRejected directly
if (this.status === MyPromise.REJECTED) {
onRejected(this.reason)
}
}
}
Copy the code
Some instructions for then functions
- Under what circumstances
this.status
Will bepending
State, under what circumstances can it beresolved
state
This is also related to the event loop mechanism, as follows:
// This. status is pending
new MyPromise((resolve, reject) = > {
setTimeout((a)= > {
resolve(1)},0)
}).then(value= > {
console.log(value)
})
// This. Status is Resolved
new MyPromise((resolve, reject) = > {
resolve(1)
}).then(value= > {
console.log(value)
})
Copy the code
- What is passthrough
As shown in the following code, when no arguments are passed to the then, the Promise passes the result to the next THEN using the methods defined by the internal default.
let p1 = new MyPromise((resolve, reject) = > {
setTimeout((a)= > {
resolved('It worked');
}, 1000);
})
p1.then().then((res) = > {
console.log(res);
})
Copy the code
Because we don’t currently support chained calls, this code will run in trouble.
Support for chain calls
Support for chain calls is simple, we just need to return this at the end of the then function, which supports chain calls:
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
// ...
return this; }}Copy the code
After each call to THEN, we return the current Promise object, since the then method exists on the Promise object, and we simply implement the simple call to the Promise.
This is the time to run the pass-through test code above.
But the above code still has corresponding problems, look at the following code:
const p1 = new MyPromise((resolved, rejected) = > {
resolved('resolved');
});
p1.then((res) = > {
console.log(res);
return 'then1';
})
.then((res) = > {
console.log(res);
return 'then2';
})
.then((res) = > {
console.log(res);
return 'then3';
})
Resolved -> then1 -> then2
Resolved -> resolved -> resolved
Copy the code
The output is different from our expectation, because the this returned in then represents P1. After new MyPromise, the state has changed from pending to Resolved and will not change again. So this. Value in MyPromise will always be Resolved.
At this point, we need to look at the return value of then.
Then the return value
In fact, then always returns a new Promise object.
Take a look at the following code:
// Create a promise
const aPromise = new Promise(function (resolve) {
resolve(100);
});
// then returns the promise
var thenPromise = aPromise.then(function (value) {
console.log(value);
});
console.log(aPromise ! == thenPromise);// => true
Copy the code
From the code above we can see that the Promise returned by the then method is no longer the original Promise, as shown below (from the Promise mini-book) :
Promise’s chain call is different from the jQuery chain call. The jQuery chain call returns the same jQuery object. Promises are more like array methods, such as slice, that return a new value after each operation.
Modified code
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
onRejected = typeof onRejected === 'function' ? onRejected : reason= > {throw reason}
The then method returns a new promise
const promise2 = new MyPromise((resolve, reject) = > {
// Successful state, directly resolve
if (this.status === MyPromise.RESOLVED) {
// The ondepressing function will return the value, resolve out
let x = onFulfilled(this.value);
resolve(x);
}
// Reject
if (this.status === MyPromise.REJECTED) {
// reject the return value of the onRejected function
let x = onRejected(this.reason)
reject && reject(x);
}
// This is a big pity, onFulfilled, onRejected into the array, waiting for the callback to execute
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push((value) = > {
let x = onFulfilled(value);
resolve(x);
})
this.rejectedQueues.push((reason) = > {
letx = onRejected(reason); reject && reject(x); }}}));returnpromise2; }}Resolved -> then1 -> then2
Copy the code
There is a problem
At this point we are done with simple chain calls, but only synchronous chain calls are supported. If we need to do other asynchronous operations in the THEN method, the above code will be GG.
The following code:
const p1 = new MyPromise((resolved, rejected) = > {
resolved('我 resolved 了');
});
p1.then((res) = > {
console.log(res);
return new MyPromise((resolved, rejected) = > {
setTimeout((a)= > {
resolved('then1');
}, 1000)}); }) .then((res) = > {
console.log(res);
return new MyPromise((resolved, rejected) = > {
setTimeout((a)= > {
resolved('then2');
}, 1000)}); }) .then((res) = > {
console.log(res);
return 'then3';
})
Copy the code
The above code passes the Promise object as an argument directly to the next THEN function, when we actually want to pass the result of processing the Promise.
3rd edition (Asynchronous chain Call)
In this release we implement the asynchronous chain call of Promise.
Train of thought
OnFulfilled and onRejected will return the values of onFulfilled and onFulfilled.
// The successful function returns
let x = onFulfilled(this.value);
// Failed function returns
let x = onRejected(this.reason);
Copy the code
As can be seen from the above problems, x can be either a common value or a Promise object. The transmission of common values has been solved in the second version. What we need to solve now is how to handle when X returns a Promise object.
When x is a Promise object, we need to wait until the returned Promise state changes, and then execute the subsequent then function. The code is as follows:
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
onRejected = typeof onRejected === 'function' ? onRejected : reason= > { throw reason}
The then method returns a new promise
const promise2 = new MyPromise((resolve, reject) = > {
// Successful state, directly resolve
if (this.status === MyPromise.RESOLVED) {
// The ondepressing function will return the value, resolve out
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
}
// Reject
if (this.status === MyPromise.REJECTED) {
// reject the return value of the onRejected function
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
}
// This is a big pity, onFulfilled, onRejected into the array, waiting for the callback to execute
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push((a)= > {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.rejectedQueues.push((a)= > {
let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }}}));returnpromise2; }}Copy the code
We will write a new function resolvePromise, which is used to handle the core method of asynchronous chain calls. It will determine if the value returned by x is a Promise object. If so, the state will change after the Promise returns. Just resovle the value out:
const resolvePromise = (promise2, x, resolve, reject) = > {
if (x instanceof MyPromise) {
const then = x.then;
if (x.status == MyPromise.PENDING) {
then.call(x, y= > {
resolvePromise(promise2, y, resolve, reject);
}, err= >{ reject(err); })}else{ x.then(resolve, reject); }}else{ resolve(x); }}Copy the code
Code instructions
resolvePromise
The resolvePromise accepts four parameters:
promise2
是then
In returnpromise
;x
是then
Two parameters ofonFulfilled
oronRejected
The type is indeterminate. It could be a normal value. It could bethenable
Object;resolve
和reject
是promise2
.
Then returns the value type
When x is a Promise and its state is Pending, if x is successfully executed, the resolvePromise function is recursively called and the result of x execution is passed in as the second parameter of resolvePromise.
If the execution fails, the reject method of promise2 is called directly.
Here we have basically A complete promise. Next we need to standardize our Promises based on Promises/A+.
Specification Promise
The previous several editions of the code author is basically in accordance with the specification, here is mainly about a few points that do not conform to the specification.
Specification THEN (Specification 2.2)
Ondepressing and onRejected need to be performed asynchronously in then, that is, put them into an asynchronous task (Specification 2.2.4).
implementation
We need to wrap the then function with setTimeout and put it into a macro task. This involves js EventLoop.
class MyPromise {
// ...
private then(onFulfilled, onRejected) {
// ...
The then method returns a new promise
const promise2 = new MyPromise((resolve, reject) = > {
// Successful state, directly resolve
if (this.status === MyPromise.RESOLVED) {
// The ondepressing function will return the value, resolve out
setTimeout((a)= > {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) { reject(err); }})}// Reject
if (this.status === MyPromise.REJECTED) {
// reject the return value of the onRejected function
setTimeout((a)= > {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch(err) { reject(err); }})}// This is a big pity, onFulfilled, onRejected into the array, waiting for the callback to execute
if (this.status === MyPromise.PENDING) {
this.resolvedQueues.push((a)= > {
setTimeout((a)= > {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) { reject(err); }})})this.rejectedQueues.push((a)= > {
setTimeout((a)= > {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject);
} catch(err) { reject(err); }})})}});returnpromise2; }}Copy the code
Use microtask packages
Promise. Then is a microtask. Now, when you use the setTimeout package, it becomes a macro task.
var p1 = new MyPromise((resolved, rejected) = > {
resolved('resolved');
})
setTimeout((a)= > {
console.log('---setTimeout---');
}, 0);
p1.then(res= > {
console.log('---then---');
})
// Normal Promise: then -> setTimeout
// Our Promise: setTimeout -> then
Copy the code
The output order is different because promises are now wrapped in the setTimeout macro task.
We can improve ondepressing and onRejected by using micro-tasks. Common micro-tasks include Process. NextTick, MutationObserver, postMessage, etc. Let’s rewrite this using postMessage:
// ...
if (this.status === MyPromise.RESOLVED) {
// The ondepressing function will return the value, resolve out
Register a message event
window.addEventListener('message'.event= > {
const { type, data } = event.data;
if (type= = ='__promise') {
try {
let x = onFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
} catch(err) { reject(err); }}});// Execute immediately
window.postMessage({
type: '__promise',},"http://localhost:3001");
}
// ...
Copy the code
The implementation is simple. We listen for the Message event of the window and immediately trigger a postMessage event. The then callback is already in the microtask queue. You can see that the order of output changes to then -> setTimeout.
Of course, the internal implementation of Promise is certainly not so simple, the author here just provides a way of thinking, you can go to study a wave of interest.
Specification resolvePromise function (Specification 2.3)
Repeated references
Repeat reference, when x and promise2 are the same, then an error is reported and repeated. (Specification 2.3.1) because waiting for yourself to finish is never productive.
const p1 = new MyPromise((resolved, rejected) = > {
resolved('我 resolved 了');
});
const p2 = p1.then((res) = > {
return p2;
});
Copy the code
The type of x
Roughly divided into the following several:
- 2.3.2: when
x
Is aPromise
Then waitx
Completed or failed only after changing state (this also belongs to this category2.3.3
Because thePromise
In fact, it’s also athenable
Object) - 2.3.3: when
x
Is an object or a function, i.ethenable
Object, then therex.then
As athen
- 2.3.4: when
x
Not an object, or a function when directly willx
As a parameterresolve
To return.
Let’s focus on 2.3.3, since Prmise is also a Thenable object, so what is a Thenable object?
This is simply an object/function with then methods. All Promise objects are Thenable objects, but not all Thenable objects are not Promise objects. As follows:
let thenable = { then: function(resolve, reject) { resolve(100); }}Copy the code
Handle according to the type of x:
-
If x is not a Thenable object, call resolve of Promise2 as the result of success.
-
When x is a thenable object, the then method of X will be called. After success, the resolvePromise function will be called, and the execution result y will be passed to the resolvePromise as the new X. Until the x value is no longer a Thenable object; If it fails, call Promise2 Reject.
if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
if (typeof then === 'function') {
then.call(x, (y) = > {
resolvePromise(promise2, y, resolve, reject);
}, (err) = >{ reject(err); }}})else {
resolve(x);
}
Copy the code
Only called once
The specification (Promise/A+ 2.3.3.3.3) states that if both resolvePromise and rejectPromise are called, or if multiple calls are made to the same parameter, the first call takes precedence and all other calls are ignored, ensuring that only one change of state is performed.
We define a placeholder called outside to see if the then function executes the corresponding state-changing function, and then does not execute it again, mainly to satisfy the specification.
X is the Promise object
If x is a Promise, resolve is rejected, and the Promise is finished.
X is the Thenable object
When x is a normal Thenable function, it is possible for x to execute resolve and reject simultaneously, that is, resolve and Reject of promise2 can be executed simultaneously. I’m not going to change the values anymore. In fact, there is no problem, the following code:
/ / thenable to like
{
then: function(resolve, reject) {
setTimeout((a)= > {
resolve('I am resolve of thenable object');
reject('I'm Thenable reject')}}}Copy the code
The complete resolvePromise
The complete resolvePromise function looks like this:
const resolvePromise = (promise2, x, resolve, reject) = > {
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y= > {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err= > {
if(called)return;
called = true; reject(err); })}else{ resolve(x); }}catch (e) {
if(called)return;
called = true; reject(e); }}else{ resolve(x); }}Copy the code
Here is done, open not happy, xing not excited!
Finally, we can run a test script to see if our MyPromise meets the specification.
test
There are special scripts (Promises -aplus-tests) to help us test whether the code we write meets the Promise/A+ specification.
But it seems that only JS files can be tested, so the author will ts files into JS files, test
Add:
// The code needed to execute the test case
MyPromise.deferred = function() {
let defer = {};
defer.promise = new MyPromise((resolve, reject) = > {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
}
Copy the code
You need to install the test plug-in in advance:
#Installing test scripts
npm i -g promises-aplus-tests
#To begin testing
promises-aplus-tests MyPromise.js
Copy the code
The results are as follows:
With perfect approval, we can now look at more ways to implement Promise.
More methods
After implementing the Promise above, it’s relatively easy to write examples and static methods.
Instance methods
Promise.prototype.catch
implementation
This method is the syntactic sugar of the THEN method, just pass the onRejected parameter to the then.
private catch(onRejected) {
return this.then(null, onRejected);
}
Copy the code
Example:
const p1 = new MyPromise((resolved, rejected) = > {
resolved('resolved');
})
p1.then((res) = > {
return new MyPromise((resolved, rejected) = > {
setTimeout((a)= > {
rejected('Wrong');
}, 1000)}); }) .then((res) = > {
return new MyPromise((resolved, rejected) = > {
setTimeout((a)= > {
resolved('then2');
}, 1000)}); }) .then((res) = > {
return 'then3';
}).catch(error= > {
console.log('----error', error);
})
// after 1s: ----error Error
Copy the code
Promise.prototype.finally
implementation
The finally() method is used to specify actions that will be performed regardless of the final state of the Promise object.
private finally (fn) {
return this.then(fn, fn);
}
Copy the code
example
const p1 = new MyPromise((resolved, rejected) = > {
resolved('resolved');
})
p1.then((res) = > {
return new MyPromise((resolved, rejected) = > {
setTimeout((a)= > {
rejected('Wrong');
}, 1000)}); }) .then((res) = > {
return new MyPromise((resolved, rejected) = > {
setTimeout((a)= > {
resolved('then2');
}, 1000)}); }) .then((res) = > {
return 'then3';
}).catch(error= > {
console.log('---error', error);
return `catch-${error}`
}).finally(res= > {
console.log('---finally---', res);
})
/ / output results: - error mistake "- >" "- finally - catch - a mistake
Copy the code
A static method
Promise.resolve
implementation
Sometimes you need to turn an existing object into a Promise object, and the promise.resolve () method does this.
static resolve = (val) = > {
return new MyPromise((resolve,reject) = > {
resolve(val);
});
}
Copy the code
example
MyPromise.resolve({name: 'darrell', sex: 'boy' }).then((res) = > {
console.log(res);
}).catch((error) = > {
console.log(error);
});
{name: "Darrell ", sex: "boy"}
Copy the code
Promise.reject
implementation
The promise.Reject (Reason) method also returns a new Promise instance with a state of Rejected.
static reject = (val) = > {
return new MyPromise((resolve,reject) = > {
reject(val)
});
}
Copy the code
example
MyPromise.reject("Something went wrong.").then((res) = > {
console.log(res);
}).catch((error) = > {
console.log(error);
});
// Output result: error
Copy the code
Promise.all
The promise.all () method is used to wrap multiple Promise instances into a new Promise instance,
const p = Promise.all([p1, p2, p3]);
Copy the code
- only
p1
,p2
,p3
The state of theta becomesfulfilled
.p
Will becomefulfilled
; - As long as
p1
,p2
,p3
One of them wasrejected
.p
The state of theta becomes thetarejected
At this time, the first to bereject
Is passed top
The callback function of.
implementation
static all = (promises: MyPromise[]) = > {
return new MyPromise((resolve, reject) = > {
let result: MyPromise[] = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(data= > {
result[i] = data;
if(++count == promises.length) { resolve(result); }},error= >{ reject(error); }); }}); }Copy the code
example
let Promise1 = new MyPromise((resolve, reject) = > {
setTimeout((a)= > {
resolve('Promise1');
}, 2000);
});
let Promise2 = new MyPromise((resolve, reject) = > {
resolve('Promise2');
});
let Promise3 = new MyPromise((resolve, reject) = > {
resolve('Promise3');
})
let Promise4 = new MyPromise((resolve, reject) = > {
reject('Promise4');
})
let p = MyPromise.all([Promise1, Promise2, Promise3, Promise4]);
p.then((res) = > {
// If all three are successful, they are successful
console.log('-- It worked ', res);
}).catch((error) = > {
// If there is a failure, there is a failure
console.log('-- failed ', err);
});
// Direct output: -- failed Promise4
Copy the code
Promise.race
The promise.race () method again wraps multiple Promise instances into a new Promise instance.
const p = Promise.race([p1, p2, p3]);
Copy the code
If one instance of P1, P2, and P3 changes state first, then the state of P changes. The return value of the first changed Promise instance is passed to p’s callback.
implementation
static race = (promises) = > {
return new Promise((resolve,reject) = >{
for(let i = 0; i < promises.length; i++){ promises[i].then(resolve,reject) }; })}Copy the code
example
The example, like all, is called as follows:
// ...
let p = MyPromise.race([Promise1, Promise2, Promise3, Promise4])
p.then((res) = > {
console.log('-- It worked ', res);
}).catch((error) = > {
console.log('-- failed ', err);
});
// Direct output: -- successful Promise2
Copy the code
Promise.allSettled
This method takes a set of Promise instances as parameters and wraps them into a new Promise instance.
const p = Promise.race([p1, p2, p3]);
Copy the code
Only after all these parameter instances return the result, whether this is a pity or Rejected, and the state of the method can only become a pity.
The difference between this method and promise. all is that all requests cannot be confirmed. In all, if a Promise is rejected, P changes to Rejected immediately, and some asynchronous requests may not be completed.
implementation
static allSettled = (promises: MyPromise[]) = > {
return new MyPromise((resolve) = > {
let result: MyPromise[] = [];
let count = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].finally(res= > {
result[i] = res;
if(++count == promises.length) { resolve(result); }}}})); }Copy the code
example
The example, like all, is called as follows:
let p = MyPromise.allSettled([Promise1, Promise2, Promise3, Promise4])
p.then((res) = > {
// If all three are successful, they are successful
console.log('-- It worked ', res);
}, err= > {
// If there is a failure, there is a failure
console.log('-- failed ', err);
})
["Promise1", "Promise2", "Promise3", "Promise4"]
Copy the code
conclusion
In this article, I take you step by step to achieve the Promise in line with the Promise/A+ specification, after reading I believe that you can basically write A Promise independently.
Finally, you can see how well you have mastered the following questions:
Promise
How is callback function return value penetration implemented in?Promise
After the error, how do you get throughThe bubblingPass it to the last function that catches the exception?Promise
How do you support chained calls?- How to set the
Promise.then
Packaged as a microtask?
Honestly, I want a thumbs up!
Reference documentation
- Ruan ES6 Promise tutorial
- Promise a book
- Promises/A+ Specification document
- Analyze the internal structure of Promise, step by step to achieve a complete Promise class that can pass all Test cases
- 30 minutes to make sure you understand the Promise principle
- Manually implement a Promise that satisfies Promises – aplus-Tests
- Interviewer: Please describe in one sentence what JS exceptions can be caught by try catch
The sample code
Sample code can be seen here:
- Promise sample code