Deepen the understanding of promise, step by step to implement and improve promise and API implementation.
Resolve and Reject are implemented
Resolve and reject are used to determine the state of a Promise object after a new Promise. The sequence of the two functions in the code determines the state of the Promise object. Once the state changes, it is not modified
The difficulty of implementation is that the promise state cannot be modified again after the state changes.
Console. log('start') const p1 =new promise ((resolve,reject)=>{console.log("doing")) resolve(1) reject(2) }) console.log('end',p1) // start // doing // end Promise {<fulfilled>: FULFILLESSTATUS ='pending' static FULFILLESSTATUS =' fulfilled' static REJECTEDSTATUS =' fulfilled 'rejected' constructor(executor){ this.PromiseState = MPromise.PENDINGSTATUS; this.PromiseResult = null; // Resolve and reject execute this as window, Bind this executor(this.resolve. Bind (this),this.reject. Bind (this))} resolve(res){if(this.promisestate === MPromise.PENDINGSTATUS){ this.PromiseResult = res; this.PromiseState = MPromise.FULFILLESSTATUS; } } reject(err){ if(this.PromiseState === MPromise.PENDINGSTATUS){ this.PromiseResult = err; this.PromiseState = MPromise.REJECTEDSTATUS; } } } console.log('start') const p2 =new MPromise((resolve,reject)=>{ console.log("doing") resolve(1) reject(2) }) console.log('end',p2) // start // doing // end MPromise { PromiseState: 'fulfilled', PromiseResult: 1 }Copy the code
The realization of the Promise. Prototype. Then
Basic usage
instructions
- Asynchronous execution
- The constructor’s executor argument is executed when resolve is called, and ondepressing is executed then
- The executor argument to the constructor calls reject, and then calls onRejected
/ / sample
console.log('start')
const p1 = new Promise((resolve, reject) = > {
resolve(1)
}).then(res= > {
console.log('p1->then', res)
})
const p2 = new Promise((resolve, reject) = > {
reject(2)
}).then(res= >{},err= > {
console.log('p2->then', err)
})
console.log('end')
// start
// end
// p1->then 1
// p2->then 2
Copy the code
Implementation idea:
- The then function is used to register the callback function, onFulfilled, and the onFulfilled callback function is added to the queue
- The resolve/reject function executes the callback asynchronously
Take new Promise(resolve=>resolve(1)). Then (res=> RES) as an example to illustrate the implementation process
-
Pending state: Synchronization code is executed
- perform
resolve=>resolve(1)
Function, executeresolve(1)
function - The code to be executed is encapsulated as function fn and added to the micro task. The functions of this function include: modifying the state to be fulfilled, modifying the PromiseResult value, and executing the function in the resolvedQueue
- perform
then(res=>res)
Function, add ondepressing function namelyres=>res
To the queue array resolvedQueue
- perform
-
Pending → depressing state: Asynchronous execution of FN in micro-task
- The state is modified as depressing
- PromiseResult value change
- The arrays in the resolvedQueue execute sequentially
class MPromise {
static PENDINGSTATUS = 'pending'
static FULFILLESSTATUS = 'fulfilled'
static REJECTEDSTATUS = 'rejected'
constructor(executor) {
this.PromiseState = MPromise.PENDINGSTATUS;
this.PromiseResult = null;
// +++ the following code blocks are new
// Store the ondepressing, onRejected callback function which needs to be performed in then function
this.resolvedQueue = [];
this.rejectedQueue = [];
// +++
// Resolve and reject execute this with the value window and bind this
executor(this.resolve.bind(this), this.reject.bind(this));
}
resolve(res) {
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// Use if to determine the state, so that the state cannot be changed again
// +++ the following code blocks are new
// This is the big pity function that can be saved in the resolvedQueue asynchronously
const fn = () = > {
this.PromiseResult = res;
this.PromiseState = MPromise.FULFILLESSTATUS;
this.resolvedQueue.length && this.resolvedQueue.forEach(cb= > cb(res))
}
// Enable the microqueue
resultHandlerAsync(fn)
// +++}}reject(err) {
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// +++ the following code blocks are new
// Run the onRejected function in the rejectedQueue asynchronously
const fn = () = > {
this.PromiseResult = err;
this.PromiseState = MPromise.REJECTEDSTATUS;
this.rejectedQueue.length && this.rejectedQueue.forEach(cb= > cb(err))
}
// Enable the microqueue
resultHandlerAsync(fn)
// +++}}// +++ the following code blocks are new
then(onFulfilled, onRejected) {
// The then function registers the callback function, which is to be performed ondepressing, and the onRejected callback function is added to the queue
if (this.PromiseState === MPromise.PENDINGSTATUS) {
this.resolvedQueue.push(onFulfilled)
this.rejectedQueue.push(onRejected)
}
}
// +++
}
/ / test
console.log('start')
const p3 = new MPromise((resolve, reject) = > {
resolve(1)
}).then(res= > {
console.log('p3->then', res)
})
const p4 = new MPromise((resolve, reject) = > {
reject(2)
}).then(res= >{},err= > {
console.log('p4->then', err)
})
console.log('end')
// start
// end
// p3->then 1
// p4->then 2
Copy the code
Chain calls to THEN
Functions to be implemented are as follows:
- OnFulfilled, onFulfilled. The callback function returns the value or Error as parameter to the next onFulfilled, onFulfilled
- Then is still a Promise object after execution
/ / sample
console.log('start')
new Promise(resolve= > {
resolve(1)
})
.then(res= > res + 1)
.then(res= > {
console.log("resolve(1) ~ then:return 1+1 ~ then: res", res)
})
new Promise(resolve= > {
resolve(2)
})
.then(res= > { throw new Error(res + 1) })
.then(res= > { console.log(res) }, err= > {
console.log("resolve(2) ~ then:throw Err 2+1 ~ then: err", err)
})
new Promise((resolve, reject) = > {
reject(3)
})
.then(() = >{},err= > err + 1)
.then(res= > {
console.log("reject(3) ~ then:return 3+1 ~ then: res", res)
})
new Promise((resolve, reject) = > {
reject(4)
})
.then(() = >{},err= > { throw new Error(err+1) })
.then(res= > { console.log(res) }, err= > {
console.log("reject(4) ~ then:throw Err 4+1 ~ then: err", err)
})
console.log('end')
// start
// end
// resolve(1) ~ then:return 1+1 ~ then: res 2
// resolve(2) ~ then:throw Err 2+1 ~ then: err Error: 3
// reject(3) ~ then:return 3+1 ~ then: res 4
// reject(4) ~ then:throw Err 4+1 ~ then: err Error: 5
Copy the code
Implementation idea:
- Then creates a promise and returns it
- Push to resolvedQueue/rejectedQueue function includes two steps, implement onFulfilled/onRejected and resolve/reject.
Use new Promise(resolve=>resolve(1)).then(res=> RES +1).then(res=>console.log(res)) as an example to illustrate the implementation process
-
Pending state: Synchronization code is executed
new Promise
Perform:resolve=>resolve(1)
performresolve(1)
Execution: The code to be executed is encapsulated into the function FN and added to the micro task. The functions of FN include: modifying the state to be fulfilled, modifying the PromiseResult value, and executing the function in the resolvedQueue- Then perform: This function will perform onFulfilled/onRejected and resolve/Reject (onFulfilled/onRejected returns). This function will perform onFulfilled/onRejected (onFulfilled/onRejected returns).
class MPromise {
static PENDINGSTATUS = 'pending'
static FULFILLESSTATUS = 'fulfilled'
static REJECTEDSTATUS = 'rejected'
constructor(executor) {
this.PromiseState = MPromise.PENDINGSTATUS;
this.PromiseResult = null;
// Store the ondepressing, onRejected callback function which needs to be performed in then function
this.resolvedQueue = [];
this.rejectedQueue = [];
// Resolve and reject execute this with the value window and bind this
executor(this.resolve.bind(this), this.reject.bind(this));
}
resolve(res) {
// the following code has been modified
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// Use if to determine the state, so that the state cannot be changed again
// Asynchronously execute the ondepressing function saved in the resolvedQueue
// Enable the microqueue
resultHandlerAsync(() = > {
this.PromiseResult = res;
this.PromiseState = MPromise.FULFILLESSTATUS;
if (this.resolvedQueue.length) {
const cb = this.resolvedQueue.shift();
cb(res)
}
})
}
// +++
}
reject(err) {
// the following code has been modified
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// Run the onRejected function in the rejectedQueue asynchronously
// Enable the microqueue
resultHandlerAsync(() = > {
this.PromiseResult = err;
this.PromiseState = MPromise.REJECTEDSTATUS;
if (this.rejectedQueue.length) {
const cb = this.rejectedQueue.shift();
cb(err)
}
})
}
// +++
}
then(onFulfilled, onRejected) {
// The then function registers the callback function, which is to be performed ondepressing, and the onRejected callback function is added to the queue
// the following code has been modified
return new MPromise((resolve, reject) = > {
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// This is a big pity. The return values will be passed to the parameters of ondepressing in the next then
this.resolvedQueue.push((res) = > {
try {
const result = onFulfilled(res);
resolve(result);
} catch (err) {
reject(err)
}
});
this.rejectedQueue.push((err) = > {
try {
const result = onRejected(err);
resolve(result);
} catch(err) { reject(err) } }); }})// +++}}function resultHandlerAsync(cb) {
// console.log('async')
// This function will start a microtask, cb into the microtask execution
const observer = new MutationObserver(cb);
observer.observe(document.body, { attributes: true });
document.body.className = `The ${Math.random()}`;
}
console.log('start')
new MPromise(resolve= > {
resolve(1)
})
.then(res= > res + 1)
.then(res= > {
console.log("resolve(1) ~ then:return 1+1 ~ then: res", res)
})
new MPromise(resolve= > {
resolve(2)
})
.then(res= > { throw new Error(res + 1) })
.then(res= > { console.log(res) }, err= > {
console.log("resolve(2) ~ then:throw Err 2+1 ~ then: err", err)
})
new MPromise((resolve, reject) = > {
reject(3)
})
.then(() = >{},err= > err + 1)
.then(res= > {
console.log("reject(3) ~ then:return 3+1 ~ then: res", res)
})
new MPromise((resolve, reject) = > {
reject(4)
})
.then(() = >{},err= > { throw new Error(err+1) })
.then(res= > { console.log(res) }, err= > {
console.log("reject(4) ~ then:throw Err 4+1 ~ then: err", err)
})
console.log('end')
// start
// end
// resolve(1) ~ then:return 1+1 ~ then: res 2
// resolve(2) ~ then:throw Err 2+1 ~ then: err Error: 3
// reject(3) ~ then:return 3+1 ~ then: res 4
// reject(4) ~ then:throw Err 4+1 ~ then: err Error: 5
Copy the code
Arguments to then are non-functions
/ / sample
new Promise((resolve, reject) = > {
resolve(1)
}).then().then(res= > { console.log("resolve(1) ~ undefined ~ then: res", res) })
new Promise((resolve, reject) = > {
reject(2)
}).then().then(undefined.err= > { console.log("reject(2) ~ undefined ~ then: err", err) })
// resolve(1) ~ undefined ~ then: res 1
// reject(2) ~ undefined ~ then: err 2
Copy the code
Implementation idea:
Check whether the passed argument is a function, if so, do nothing; If not, create a function that returns the value of resolve or throws an error
then(onFulfilled, onRejected) {
// +++ The following code is new
onFulfilled = (typeof onFulfilled === 'function')? onFulfilled :res= > res;
onRejected = (typeof onRejected === 'function')? onRejected :err= > { throw err };
// +++
return new MPromise(...)
}
new MPromise((resolve, reject) = > {
resolve(1)
}).then().then(res= > { console.log("resolve(1) ~ undefined ~ then: res", res) })
new MPromise((resolve, reject) = > {
reject(2)
}).then().then(undefined.err= > { console.log("reject(2) ~ undefined ~ then: err", err) })
// resolve(1) ~ undefined ~ then: res 1
// reject(2) ~ undefined ~ then: err 2
Copy the code
Promise. Prototype. Catch and Promise. The prototype. The realization of the finally
The catch method returns a Promise and handles the rejection.
The finally method returns a Promise. At the end of the promise, the specified callback function will be executed, whether the result is fulfilled or Rejected
/ / sample
new Promise((resolve, reject) = > {
reject(1)
})
.catch(err= > {
console.log("reject(1) ~ then: err", err)
})
new Promise((resolve, reject) = > {
reject(2)
})
.catch()
.then(() = >{},err= > {
console.log("reject(2) ~ catch:undefined ~ then: err", err)
})
new Promise((resolve, reject) = > {
reject(3)
})
.catch(err= > err + 1)
.finally((value) = > {
console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22", value);
return 22;
})
.then(res= > {
console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then: err", res)
})
// reject(1) ~ then: err 1
// reject(2) ~ catch:undefined ~ then: err 2
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 undefined
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then:err 4
Copy the code
Implementation:
Catch method = then(undefined,onRejected)
Finally method: Asynchronously executes passed callback arguments while passing the result of the previous THEN to the next
catch(onRejected) {
return this.then(undefined,onRejected)
}
finally(cb){
return new MPromise((resolve,reject) = >{
if (this.PromiseState === MPromise.PENDINGSTATUS) {
this.resolvedQueue.push((res) = > {
try {
cb()
resolve(res);
} catch (err) {
reject(err)
}
});
this.rejectedQueue.push((err) = > {
try {
cb();
resolve(err);
} catch(err) { reject(err) } }); }})}new MPromise((resolve, reject) = > {
reject(1)
})
.catch(err= > {
console.log("reject(1) ~ then: err", err)
})
new MPromise((resolve, reject) = > {
reject(2)
})
.catch()
.then(() = >{},err= > {
console.log("reject(2) ~ catch:undefined ~ then: err", err)
})
new MPromise((resolve, reject) = > {
reject(3)
})
.catch(err= > err + 1)
.finally((value) = > {
console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22", value);
return 22;
})
.then(res= > {
console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then: err", res)
})
// reject(1) ~ then: err 1
// reject(2) ~ catch:undefined ~ then: err 2
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 undefined
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then:err 4
Copy the code
Promise. Resolve and Promise. Reject are implemented
The promise. resolve method returns a Promise object resolved with the given value
The promise. reject method returns a Promise object with a reason for the rejection
implementation
static resolve(res) {
return new MPromise(resolve= > resolve(res))
}
static reject(res) {
return new MPromise((resolve, reject) = > reject(res))
}
Copy the code
Promise.all and promise. race are implemented
The promise.all () method takes an iterable type of a Promise (Array, Map, Set) and returns only one Promise object. The return value is the result of all resolve, and if any reject callback executes, an error is immediately thrown.
The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected.
/ / sample
const p1 = new Promise((resolve, reject) = > {
setTimeout(resolve, 500.'one');
});
const p2 = new Promise((resolve, reject) = > {
setTimeout(resolve, 100.'two');
});
const p3 = new Promise((resolve, reject) = > {
setTimeout(reject, 300.'three');
});
Promise.race([p1, p2]).then((value) = > {
console.log('Promise.race', value);
});
Promise.all([p1, p2, p3]).then((value) = > {
console.log('Promise.all ~ then:res', value);
}).catch(err= >{console.log('Promise.all ~ catch:err', err)});
Promise.all([p1, p2]).then((value) = > {
console.log('Promise.all', value);
});
// Promise.race two
// Promise.all ~ catch:err three
// Promise.all ['one', 'two']
Copy the code
static race(promiseList) {
return new MPromise((resolve, reject) = > {
promiseList.forEach(p= > p.then(res= > resolve(res), err= > reject(err)))
})
}
static all(promiseList) {
return new MPromise((resolve, reject) = > {
let promiseArr = [];
promiseList.forEach(p= > {
p.then(res= > {
promiseArr.push(res)
}, err= > {
throw new Error(err); reject(err); }) }) resolve(promiseArr); })}Copy the code
The complete code
class MPromise {
static PENDINGSTATUS = 'pending'
static FULFILLESSTATUS = 'fulfilled'
static REJECTEDSTATUS = 'rejected'
constructor(executor) {
this.PromiseState = MPromise.PENDINGSTATUS;
this.PromiseResult = null;
// Store the ondepressing, onRejected callback function which needs to be performed in then function
this.resolvedQueue = [];
this.rejectedQueue = [];
// Resolve and reject execute this with the value window and bind this
executor(this.resolve.bind(this), this.reject.bind(this));
}
resolve(res) {
// console.log('resolve')
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// Use if to determine the state, so that the state cannot be changed again
// Asynchronously execute the ondepressing function saved in the resolvedQueue
// Enable the microqueue
resultHandlerAsync(() = > {
this.PromiseResult = res;
this.PromiseState = MPromise.FULFILLESSTATUS;
if (this.resolvedQueue.length) {
const cb = this.resolvedQueue.shift();
cb(res)
}
})
}
}
reject(err) {
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// Run the onRejected function in the rejectedQueue asynchronously
// Enable the microqueue
resultHandlerAsync(() = > {
this.PromiseResult = err;
this.PromiseState = MPromise.REJECTEDSTATUS;
if (this.rejectedQueue.length) {
const cb = this.rejectedQueue.shift();
cb(err)
}
})
}
}
then(onFulfilled, onRejected) {
// The then function registers the callback function, which is to be performed ondepressing, and the onRejected callback function is added to the queue
onFulfilled = (typeof onFulfilled === 'function')? onFulfilled :res= > res;
onRejected = (typeof onRejected === 'function')? onRejected :err= > { throw err };
return new MPromise((resolve, reject) = > {
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// This is a big pity. The return values will be passed to the parameters of ondepressing in the next then
this.resolvedQueue.push((res) = > {
try {
const result = onFulfilled(res);
resolve(result);
} catch (err) {
reject(err)
}
});
this.rejectedQueue.push((err) = > {
try {
const result = onRejected(err);
resolve(result);
} catch(err) { reject(err) } }); }})}catch(onRejected) {
return this.then(undefined, onRejected)
}
finally(cb) {
return new MPromise((resolve, reject) = > {
if (this.PromiseState === MPromise.PENDINGSTATUS) {
// This is a big pity. The return values will be passed to the parameters of ondepressing in the next then
this.resolvedQueue.push((res) = > {
try {
cb()
resolve(res);
} catch (err) {
reject(err)
}
});
this.rejectedQueue.push((err) = > {
try {
cb();
resolve(err);
} catch(err) { reject(err) } }); }})}static resolve(res) {
return new MPromise(resolve= > resolve(res))
}
static reject(res) {
return new MPromise((resolve, reject) = > reject(res))
}
static race(promiseList) {
return new MPromise((resolve, reject) = > {
promiseList.forEach(p= > p.then(res= > resolve(res), err= > reject(err)))
})
}
static all(promiseList) {
return new MPromise((resolve, reject) = > {
let promiseArr = [];
promiseList.forEach(p= > {
p.then(res= > {
promiseArr.push(res)
}, err= > {
throw new Error(err); reject(err); }) }) resolve(promiseArr); }}})function resultHandlerAsync(cb) {
// This function will start a microtask, cb into the microtask execution
const observer = new MutationObserver(cb);
observer.observe(document.body, { attributes: true });
document.body.className = `The ${Math.random()}`;
}
Copy the code