This paper tries to understand the realization principle of Promise from a new perspective, that is, to realize a Promisee that conforms to all the behaviors of Promise step by step through some external manifestations of Promise.
Although it is for small white users, but if the use of the Promise of nothing, forgive the old man is powerless, or first to learn the use of Promise.
Let’s work on our own promise with a few test cases for Promise, hoping readers will open up the editor and follow the steps in this article to tap into the code. Take a look at the work that goes into implementing each action of a Promise, so that at the end of the day the inner workings of promises are within our grasp. Let’s call the Promise we want to fulfill MyPromise. Let’s start by creating a new MyPromise constructor. At first MyPromise is an empty function.
function MyPromise(fn){
} Copy the code
Fn is the callback that the user passes to a Promise when creating a Promise instance, and we know that fn has two arguments: resolve and reject.
0. Test fn synchronization
We know that the callback FN received by the Promise is executed synchronously, and the following code prints 1, then 2
Var a = new Promise((resolve, reject) => {console.log(1)}); console.log(2)Copy the code
So our MyPromise will change to
function MyPromise(fn){
fn()
} Copy the code
So our MyPromise has the behavior that promises execute synchronously.
As fn accepts resolve and reject, we define two functions inside MyPromise called resolve and Reject and pass them to fn as arguments. An instance of a Promise has a THEN parameter, which we define together.
function MyPromise(fn){
function resolve(){
}
function reject(){
}
fn(resolve,reject);
this.then=function(onFullfilled,onRejected){}
} Copy the code
1, test resolve, reject, then
The first function passed to THEN, the success callback, is called in Resolve and the value of resolve is passed to the success callback. Reject calls the second function passed to THEN, the failure callback, and passes the value of REJECT to the failure callback.
Var a = new Promise((resolve, reject) => {resolve())'success'); }); A.hen ((val) => {console.log(val) // print success})'error'); }); B.teng ((val) => {console.log(val)}, (error) => {console.log(error) // prints error})Copy the code
We define an array of deffers to hold successful and failed callbacks passed in by the THEN function, optionally executing either resolve or reject. Value and status are also declared. Value represents the final value that the current Promise will change, which is the error value of resolve or reject. Status represents the status of a Promise, originally pending and represented by NULL. Reoslve changed this to true, reject to false. You can only change it once, whether it’s true or false.
functionMyPromise(fn){ var value; Var status=null; // Resolve or reject; // State of the Promise, null: initial,true: Resolve,falseVar deffers=[] // Callback array, each timethen, / / to push a {onFullFilled: onFullFilled onRejected: onRejected} the callback. // Call onFullFilled or onRejected fn(resolve,reject).function resolve(val){
value=val;
status=true;
final()
}
function reject(val){
value=val;
status=false; Final ()} // iterate over functions in deffersfunction final() {for(var i=0,len=deffers.length; i<len; I++) {handle (deffers [I]) / / deffers [I] = > {onFullFilled: onFullFilled onRejected: onRejected}}} / / processing results of real function, according to the status value judgmentfunction handle(deffer){ //deffer=>{onFullfilled:onFullfilled,onRejected:onRejected}
if(status===true){
deffer.onFullfilled && deffer.onFullfilled(value)
}
if(status===false){
deffer.onRejected && deffer.onRejected(value)
}
}
this.then=function(onFullfilled,onRejected){ var deffer = {onFullfilled:onFullfilled,onRejected:onRejected} deffers.push(deffer); handle(deffer); }}Copy the code
2 Test asynchronous resolve and asynchronous Reject
Var b = new Promise((resolve, reject) => {setTimeout(() => {
resolve('success');
}, 1000);
});
b.then((val) => {
console.log(val, ' async'(error) => {console.log(error,' async'Var b = new Promise((resolve, reject) => {setTimeout(() => {
reject('error');
}, 1000);
});
b.then((val) => {
console.log(val, ' async')
}, (error) => {
console.log(error, ' async'Error async})Copy the code
This part of the code has already been implemented logically in the previous step and will not be repeated
3 Test whether resolve and reject are called
Even if both resolve and Reject are called for a Promise, the Promise state changes only once, depending on the order in which it is called.
Var b = new Promise((resolve, reject) => {resolve())"success");
reject('error');
});
b.then((val) => {
console.log(val, 'Simultaneous call'}, (error) => {console.log(error,'Simultaneous call'Reject var b = new Promise((resolve, reject) => {setTimeout(()=>{
resolve("success");
},100)
reject('error');
});
b.then((val) => {
console.log(val, 'Simultaneous call')
}, (error) => {
console.log(error, 'Simultaneous call') // Print error while calling})Copy the code
Our MyPromise implementation is as follows, Posting only the modified parts
function MyPromise(fn){
...
//fn(resolve,reject);
doResolve(fn, Resolve,reject) // Call fn insteaddoResolve,doCall fn in Resolvefunction doResolve(fn,resolve,reject){
var hasChange =false;
fn((value)=>{
if(hasChange){
return;
}
hasChange=true; // Set to resolve or reject after a calltrue// I won't come in next time. Resolve (value)},(error)=>{if(hasChange){
return;
}
hasChange=true;
reject(value)
})
}
} Copy the code
4 Test calling multiple THEN’s for the same promise
A Promise can call then multiple times. As mentioned above, these callbacks are put into the Promise’s array of DEffers, which are called in turn through resolve or Reject.
//6 Tests multiple calls to the same promisethen
var b = new Promise((resolve, reject) => {
resolve('success')}); b.then((val) => { console.log(val,' then1')
}, (error) => {
console.log(error, ' then1')
})
b.then((val) => {
console.log(val, ' then2')
}, (error) => {
console.log(error, ' then2')})Copy the code
5 Test the case of successive calls to THEN
The return value of the Promise call to the then method is a new Promise. This new Promise will accept the success callback of the previous Promise’s then callback and call resolve or reject, so
var b = new Promise((resolve, reject) => {
resolve('success')}); var newP = b .then((val) => { console.log(val,' then1');
return 'Successful callback into the second THEN'// If b is resolved, newP will resolve the return value of the callback}, (error) => {console.log(error,' then1');
return 'Failed callback into second THEN'// If b is rejected, newP rejects the return value of the callback}); newP.then((val) => { console.log(val,' then2');
}, (error) => {
console.log(error, ' then2');
})Copy the code
Here we need to modify the then method
function MyPromise(fn){
...
this.then=function(onFullfilled,onRejected){
return new MyPromise((resolve,reject)=>{
let deffer = {onFullfilled:onFullfilled,onRejected:onRejected,resolve:resolve,reject:reject};
deffers.push(deffer)
handle(deffer)
})
}
}Copy the code
There are a few changes. The first is to make the then method return a new MyPromise. The second is that every object in the Deffers array caches the resolve and Reject of the new MyPromise. After the successful callback of the previous MyPromise executes, we can pull out the new MyPromise’s resolve execution, forming a pipe. Reject the same. Now let’s deal with the logic of the MyPromise chain call, focusing on the Handle function
functionMyPromise(fn){ ... // The actual function to process the result, as judged by the value of statusfunctionhandle(deffer){ //deffer=>{onFullfilled:onFullfilled,onRejected:onRejected,resolve:resolve,reject:reject} The deffers cache the new Promise's resolve and reject. //cb is the handler. Var cb = status===true? deffer.onFullfilled : deffer.onRejected; // If no handler is passed, such as resolve, but no successful callback is passed, we execute the new Promise's resolve, passing the successful result later.if(cb==null){
status==true? deffer.resolve(value):deffer.reject(value);
return; } var val; {val=cb(value);} // We use a try catch because success and failure callbacks are user-defined and may fail. }catch(e){ deffer.reject(e);return; Deffer.resolve (val)}} deffer.resolve(val)}} deffer.resolve(val)}Copy the code
At this point, by combining the THEN and Handle methods, the logic that MyPromise can be chained to is handled. Handle handles the case when a call to THEN does not return a callback, as well as the case when the passed callback fails to execute. So the following tests were passed
// call continuouslythen
var b = new MyPromise((resolve, reject) => {
resolve('success')}); b.then(value=>{ console.log(value,'then1 calls then continuously'); // It will print, value='success'
return 'new data'
}, (error) => {
console.log(error, ' then1'}).then(value=>{console.log(value) // this will print, value='new data'}) / / testthenVar b = new MyPromise((resolve, reject) => {resolve()'success')}); b.then(null, (error) => { console.log(error,' then1')
}).then((val) => {
console.log(val, Then2 then1 failed to callback}, (error) => {console.log(error,Then2 then1 failed to callback)}); var b = new MyPromise((resolve, reject) => { reject('error')}); b.then((value) => { console.log(value,' then1')
}, null).then((val) => {
console.log(val, 'then2 then1 has no failed callback')
}, (error) => {
console.log(error, 'then2 then1 has no failed callback') // This will print, because the previous MyPromise didn't pass a failed callback, it will enter here}); Var b = new MyPromise((resolve, reject) => {resolve())'error')}); b.then((value) => { throw"Make a mistake";
return 333;
}).then((val) => {
console.log(val, 'then2 then1 execution error')
}, (error) => {
console.log(error, 'then2 then1 execution error') // It will print here becausethen});Copy the code
This is where our logic for promises is pretty much complete. Wait a second. Sometimes we resolve a new Promise, or return a new Promise in a callback to then.
Resolve a Promise or then callback that returns a new Promise
Let’s deal with this. The logic is mostly in the resolve method defined inside MyPromise, where the resolve parameter value may be a new Promise
function MyPromise(fn){
...
functionResolve (val){// Use the duck test to determine that val is a Promise object, as long as there is onethenThe method will doif (val && (typeof val === "object" || typeof val === "function")) {
var then = val.then;
if (typeof then= = ="function"If val is a Promise, pass the current Promise's resolve and reject as callbacks to valthenResolve and reject are called when val's state changes, and we get the final value that val passes to usdoResolve(then.bind(val), resolve, reject);
return; } // Val is a non-promise object. status=true;
final()
}
}Copy the code
That’s the resolution of the Promise object, but there are still some details to be worked out. If you can see this and understand it, then you can use Promise to know what it is and why.