Promise base correlation
The Promise object is used to represent the final completion (or failure) of an asynchronous operation and its resulting value. Promise-Javascript | MDN
Here is the specification document for promise the Promise A+ specification
Promise is a common knowledge in front-end work, study and interview. It is not enough for me to use it skillfully, so it is necessary to realize it in person. Let’s start!!
Manual implementation
Implementation approach
- The Promise accepts an immediate function that takes two callback arguments, resolve,reject
- A promise has three states: Pending, depressing, and Rejected. And it can only change from pending to the other two states
- Proise’s then method takes the changed promise state and returns a new Promise object
- The implementation of the callback value handler function in the then method
- Promise’s static methods, resolve, Reject, Race,all
Create a new TjfPromise class, pass in the callback argument and execute it immediately
// Define the three states of promise
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class TjfPromise{
constructor(callback){
this.status = PENDING // The initial state is pending
this.value = null
this.reason = null
try{
// The function passed in the promise is executed immediately. Note the this reference in the call
// The resolve,reject functions in promise call internally implemented functions
callback(this.resolve.bind(this), this.reject.bind(this))}catch(error){
this.reject(error)
}
}
// The promise state can only be fulfilled pending --> depressing or pending --> Rejected
// When the resolve method is called, the status becomes depressing
resolve(value){
if(this.status === PENDING){
this.status = FULFILLED
this.value = value
}
}
When the reject method is called, status changes to Rejected
reject(reason){
if(this.status === PENDING){
this.status = REJECTED
this.reason = reason
}
}
}
Copy the code
Initial implementation of then method
class TjfPromise {...then(onFulfilled, onRejected){
switch(this.status){
case FULFILLED:
onFulfilled(this.value)
break
case REJECTED:
onRejected(this.reason)
break}}}Copy the code
Test the current code with two small examples
new TjfPromise((resolve, reject) = > {
resolve(11)
}).then(value= > console.log(value))
Copy the code
The results are printed out 11, indicating the current preliminary implementation.
There are three problems here. The first problem is that there is only a judgment about the fulfilled and rejected states in the then method. When the promise is pending, the code is not executed immediately, that is, the asynchronous code is not processed. Promise, after all, is designed primarily to handle asynchronous tasks.
The second problem is that when the THEN method is called multiple times (non-chained calls), the contents of the previous THEN may not have been executed and need to be stored
The third problem is that the THEN function supports chained calls. The Promise A+ specification states that then must return A Promise object
Let’s address the first issue, support for asynchronous calls
Then method support for asynchrony
class TjfPromise{
constructor(callback){
this.status = PENDING
this.value = null
this._status = PENDING // Add a variable to the status set and get
this.reason = null
// Add two new variables to store pending callback functions
this.FULFILLED_CALLBACK = null
this.REJECTED_CALLBACK = null
try{
callback(this.resolve.bind(this), this.reject.bind(this))}catch(error){
this.reject(error)
}
}
get status() {/ / add the get
return this._status
}
set status(newStatus) {/ / the new set
this._status = newStatus // use _status to prevent nested sets from falling into an infinite loop
if(newStatus === FULFILLED){ // Call the stored callback immediately when the state changes
this.FULFILLED_CALLBACK(this.value)
}
if(newStatus === REJECTED){
this.REJECTED_CALLBACK(this.reason)
}
}
resolve(value){
if(this.status === PENDING){
this.value = value // The assignment must precede the state change
this.status = FULFILLED
}
}
reject(reason){
if(this.status === PENDING){
this.reason = reason
this.status = REJECTED
}
}
then(onFulfilled, onRejected){
switch(this.status){
case FULFILLED:
onFulfilled(this.value)
break
case REJECTED:
onRejected(this.reason)
break
casePENDING:// Pending stores the callback function until it is called
this.FULFILLED_CALLBACK = onFulfilled
this.REJECTED_CALLBACK = onRejected
}
}
}
Copy the code
Let’s test the current code
new TjfPromise((resolve, reject) = > {
setTimeout(() = > {
resolve(11)},1000)
}).then((value) = > console.log(value))
Copy the code
If 11 is printed after a delay of one second, there is no problem. Let’s solve the second problem, the multiple calls to THEN
Then method support for multiple calls
We need to make some changes to the code we just described and store the callbacks in an array
class TjfPromise{
constructor(){
// Change the variable we just used to store the pending callback function into two arrays
this.FULFILLED_CALLBACK_LIST = []
this.REJECTED_CALLBACK_LIST = []
}
set status(newStatus) {// Make some changes in the setter
this._status = newStatus
if(newStatus === FULFILLED){ // Execute all saved callbacks in sequence
this.FULFILLED_CALLBACK_LIST.forEach( callback= > callback(this.value))
}
if(newStatus === REJECTED){
this.REJECTED_CALLBACK_LIST.forEach( callback= > callback(this.reason))
}
}
then(onFulfilled, onRejected){ // Change the then method
switch(this.status){
case FULFILLED:
onFulfilled(this.value)
break
case REJECTED:
onRejected(this.reason)
break
casePENDING:// Pending stores the callback function until it is called
this.FULFILLED_CALLBACK_LIST.push(onFulfilled)
this.REJECTED_CALLBACK_LIST.push(onRejected)
}
}
Copy the code
Test the
const promise = new TjfPromise((resolve, reject) = > {
setTimeout(() = > {
resolve(11)},1000)
})
promise.then( value= > console.log(1,value))
promise.then( value= > console.log(2,value))
promise.then( value= > console.log(3,value))
Copy the code
Print the result
1 11
2 11
3 11
Copy the code
No problem, the second problem is solved, and now comes the third and most troublesome problem, the return of the then function
Perfection of THEN method
The implementation here follows the promise A+ document, which is cumbersome
Determination of callback parameters in then method
The then method should be treated as a direct return function if the argument passed is not or does not pass a value
class TjfPromise{...then(onFulfiled, onRejected){
// Process the parameters passed in
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
const realOnFulfilled = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
}
isFunction(func){ // Add a function to verify whether it is a function
return typeof func === 'function'}}Copy the code
The then method should return a promise
class TjfPromise{...then(onFulfilled, onRejected){
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
const realOnRejected = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
const promise2 = new TjfPromise((resolve, reject) = > {
switch(this.status){
case FULFILLED:
realOnFulfilled(this.value)
break
case REJECTED:
realOnRejected(this.reason)
break
casePENDING:// Pending stores the callback function until it is called
this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled)
this.REJECTED_CALLBACK_LIST.push(realOnRejected)
}
})
return promise2
}
}
Copy the code
Here we need to decide the value of realOnfulfilled. Here we start implementing the resolvePromise function
The then method will add the resolvePromise function, which will be processed with realOnFulfilled first
class TjfPromise{...then(onFulfilled, onRejected){
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
const realOnRejected = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
const promise2 = new TjfPromise((resolve, reject) = > {
try{
const x = realOnFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject) // Add the resolvePromise function
}catch(error){
reject(error)
}
switch(this.status){
case FULFILLED:
realOnFulfilled(this.value)
break
case REJECTED:
realOnRejected(this.reason)
break
case PENDING:
this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled)
this.REJECTED_CALLBACK_LIST.push(realOnRejected)
}
})
return promise2
}
}
Copy the code
Start implementing the resolvePromise function
class TjfPromise{...resolvePromise(promise2, x, resolve, reject){
// When the return value is promise2, return an error
if( x === promise2) return new TypeError('The promise and the x refer to the same')
// when x is a Tjfpromise object, its then method is called
if( x instanceof TjfPromise){
x.then(resolve, reject)
} else {
resolve(x)
}
}
}
Copy the code
Promise2 has not been initialized yet, so an error is reported. Resolve is initialized in the queueMicrotask and realOnRejected is also initialized
QueueMicrotask executes a resolvePromise
class TjfPromise{...then(onFulfilled, onRejected){
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : value= > value
const realOnRejected = this.isFunction(onRejected) ? onRejected : reason= > {throw reason}
const promise2 = new TjfPromise((resolve, reject) = > {
const fulfilledMicrotask = () = > {
try{
queueMicrotask(() = > { // call in microtask
const x = realOnFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
})
}catch(error){
reject(error)
}
}
const rejectedMicrotask = (() = > {
try{
queueMicrotask(() = > { // call in microtask
const x = realOnRejected(this.reason)
this.resolvePromise(promise2, x, resolve, reject)
})
}catch(error){
reject(error)
}
})
switch(this.status){
case FULFILLED:
fulfilledMicrotask()
break
case REJECTED:
rejectedMicrotask()
break
case PENDING:
this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
}
})
return promise2
}
}
Copy the code
At this point, the content of then function is basically completed. The following contents of resolvePromise are improved according to the promise A+ specification
Improve the resolvePromise method
class TjfPromise{...resolvePromise(promise2, x, resolve, reject){
if( x === promise2) return new TypeError('The promise and the x refer to the same')
if( x instanceof TjfPromise){
queueMicrotask(() = > { // Add another layer of microtasks, more consistent with the promise A+ call
x.then( y= > {
this.resolvePromise(promise2, y, resolve, reject)
}, reject)
})
} else if(typeof x === 'object' || this.isFunction(x)){
if(x === null) resolve(x)
let then = null
try{
then = x.then
} catch(error){
return reject(error)
}
if(this.isFunction(x.then)) {
let called = false
try{
then.call(x,(y) = > {
if(called) return
called = true
this.resolvePromise(promise2, y, resolve, reject)
}, (error) = > {
if(called) return
called = false
reject(error)
})
}catch(error){
if(called) return
reject(error)
}
} else {
resolve(x)
}
}else {
resolve(x)
}
}
}
Copy the code
Now that the resolvePromise method is done, let’s test it out
const promise = new TjfPromise((resolve, reject) = > {
resolve('success')})function other () {
return new TjfPromise((resolve, reject) = >{
resolve('other')
})
}
promise.then(value= > {
console.log(1)
console.log('resolve', value)
return other()
}).then(value= > {
console.log(2)
console.log('resolve', value)
})
Copy the code
Print the result
1
resolve success
2
resolve other
Copy the code
no problem
Implementation of static methods
Promise has a few static methods inside that can only be called through prmise.race(), not an instance, and we implement a few of them commonly used
Resolve method
The static resolve method simply returns a Promise object
class TjfPromise{...static resolve(value){
if(value instanceof TjfPromise){
return value
}
return new TjfPromise((resolve, reject) = > {
resolve(value)
})
}
}
Copy the code
Reject method
Static Reject returns a Promise object with Reason
class TjfPromise{...static reject(reason){
return new TjfPromise((resolve, reject) = > {
reject(reason)
})
}
}
Copy the code
Race method
The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected.
class TjfPromise{
static race(promiselist){
return new TjfPromise((resolve, reject) = > {
if(! promiselist.length)return resolve()
promiselist.forEach( promiseitem= > TjfPromise.resolve(promiseitem).then( value= > resolve(value), reason= > reject(reason)))
})
}
}
Copy the code
All methods
Promise. all(iterable), which returns a promise instance with value as an array, and rejected if there is one
class TjfPromise{...static all(promiselist) {
return new TjfPromise((resolve, reject) = > {
let valuelist = [];
let count = 0
if(! promiselist.length)return resolve();
promiselist.forEach((promiseitem, index) = > {
TjfPromise.resolve(promiseitem).then(
(value) = > {
valuelist[index] = value
count++
if (count === promiselist.length) resolve(valuelist)
},
(reason) = >reject(reason) ); }); }); }}Copy the code
Test the
const promise1 = new TjfPromise((resolve, reject) = > {
setTimeout(() = > {
resolve(22)},1000)})const promise2 = new TjfPromise((resolve, reject) = > {
resolve(11)
})
TjfPromise.all([promise1, promise2]).then( value= > console.log(value), reason= > console.log(reason))
Copy the code
Print the result
[22.11]
Copy the code
No problem, so that’s all the implementation of PrMISE, with all the complete code attached
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class TjfPromise {
constructor(callback) {
this.status = PENDING;
this.value = null;
this._status = PENDING;
this.reason = null;
this.FULFILLED_CALLBACK_LIST = [];
this.REJECTED_CALLBACK_LIST = [];
try {
callback(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error); }}get status() {
return this._status;
}
set status(newStatus) {
this._status = newStatus
if (newStatus === FULFILLED) {
this.FULFILLED_CALLBACK_LIST.forEach((callback) = > callback(this.value));
}
if (newStatus === REJECTED) {
this.REJECTED_CALLBACK_LIST.forEach((callback) = > callback(this.reason)); }}resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED; }}reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED; }}then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled)
? onFulfilled
: (value) = > value;
const realOnRejected = this.isFunction(onRejected)
? onRejected
: (reason) = > {throw reason};
const promise2 = new TjfPromise((resolve, reject) = > {
const fulfilledMicrotask = () = > {
try {
queueMicrotask(() = > {
const x = realOnFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
});
} catch(error) { reject(error); }};const rejectedMicrotask = () = > {
try {
queueMicrotask(() = > {
const x = realOnRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
});
} catch(error) { reject(error); }};switch (this.status) {
case FULFILLED:
fulfilledMicrotask();
break;
case REJECTED:
rejectedMicrotask();
break;
case PENDING:
this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask); }});return promise2;
}
resolvePromise(promise2, x, resolve, reject) {
if (x === promise2)
return new TypeError("The promise and the x refer to the same");
if (x instanceof TjfPromise) {
queueMicrotask(() = > {
x.then((y) = > {
this.resolvePromise(promise2, y, resolve, reject);
}, reject);
});
} else if (typeof x === "object" || this.isFunction(x)) {
if (x === null) resolve(x);
let then = null;
try {
then = x.then;
} catch (error) {
return reject(error);
}
if (this.isFunction(x.then)) {
let called = false;
try {
then.call(
x,
(y) = > {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
(error) = > {
if (called) return;
called = false; reject(error); }); }catch (error) {
if (called) return; reject(error); }}else{ resolve(x); }}else{ resolve(x); }}static resolve(value) {
if (value instanceof TjfPromise) {
return value;
}
return new TjfPromise((resolve, reject) = > {
resolve(value);
});
}
static reject(reason) {
return new TjfPromise((resolve, reject) = > {
reject(reason);
});
}
static race(promiselist) {
return new TjfPromise((resolve, reject) = > {
if(! promiselist.length)return resolve();
promiselist.forEach((promiseitem) = >
TjfPromise.resolve(promiseitem).then(
(value) = > resolve(value),
(reason) = > reject(reason)
)
);
});
}
static all(promiselist) {
return new TjfPromise((resolve, reject) = > {
let valuelist = [];
let count = 0
if(! promiselist.length)return resolve();
promiselist.forEach((promiseitem, index) = > {
TjfPromise.resolve(promiseitem).then(
(value) = > {
valuelist[index] = value
count++
if (count === promiselist.length) resolve(valuelist)
},
(reason) = > reject(reason)
);
});
});
}
isFunction(func) {
return typeof func === "function"; }}Copy the code
Reference article:
Starting with a Promise interview question that kept me awake, I went into the details of implementing promises