@[TOC] (article directory)
preface
1. Promise's meaning?
In the world of JavaScript, all code is executed by single thread. Due to this "defect", all network operations and browser events of JavaScript must be executed asynchronously. Ajax can solve this problem, but it is not easy to reuse. The chain call of JQuery can also be solved. Each call will return a JQuery object. In order to better deal with Promise, it appears, and ECMAScript regulates it.
2. What is promise a + specification?
actually Promise There are many specifications, such as Promise/A,Promise/B,Promise/D as well as Promise/A Upgraded version of Promise/A+. ES6 (also known as ES2015) adopts Promise/A+ standard.
##1, Terminology
1. promise the object or function with then method;
2. thenable is an object or function with then method;
3. value promise the value when the status is successful, resolve(value), which can be any data type (String Number Boolean undefined thenable promise);
4. Value when reason promise status fails, reject(reason);
##2, Promise status (three statuses and their relationships)
###1. pending
1.1 the initial state can be changed
1.2 it is in this state before resolve and reject
1.3 through resolve -- > Full status
1.4 pass reject -- > rejected status
###2. fulfilled
2.1 final state, unalterable
2.2 a promise is changed to this state by resolve
2.3 a value must be passed, which is the value after success
###3. rejected
3.1 final state, unalterable;
3.2 a promise is changed to this state by reject;
3.3 you must have a reason, that is, the reason for failure;
###Summary:
pending --> resolve(value) --> fulfilled
pending --> reject(reason) --> rejected
##3, Promise then method and its return value
promise should provide a then method to access the final result, whether value or reason
promise.then(onFuilled,onRejected)
##4, Norms
###1. Specification of parameters
1.1 onFulfilled must be a function type. If it is not a function type, it should be ignored (ignoring here refers to giving a default value, not ignoring in the real sense);
1.2 onRejected It must be a function type. If it is not a function type, it should be ignored (ibid.);
###2. onFulfilled feature
2.1 When promise becomes full, onFulfilled should be called, and the parameter is value; (implementation timing of onFulfilled?)
2.2 on fulfilled should not be called before promise becomes full;
2.3 it can only be called once, and several callback functions can be registered (promise.then().then().then()...); (how to call only once?)
###3. onRejected feature
3.1 when promise becomes rejected, onRejected is called, and the parameter is reason;
3.2 onRejected. Should not be called before promise becomes rejected
3.3 can only be called once
###4. onFulfilled and onRejected should be executed in the micro task phase
Why does the micro task phase execute?
You must wait until the previous task is executed before calling. Micro tasks are executed after a round of macro tasks are executed;
###5. The then method can be called multiple times
5.1 After promise is turned into full, all on fulfilled callbacks should be executed in the order of registration, which can also be understood as executing in the order of. then();
Example:
promise.then(onFulfilled , onRejected).then(onFulfilled1 => ( )).then(....)
five point two After promise becomes rejected, all onRejected callbacks should be executed in the order of registration, which can also be understood as executing in the order of. then();
###6. Return value
The specification defines that then should return a promise
const promise2 = promise( onFulfilled , onRejected );
6.1 if the execution result of onfulfilled or onRejected is x (anything: value or promise), call resolvePromise;
6.2 an exception is thrown during the execution of onfulfilled or onRejected, and promise2 needs to be rejected;
6.3 if on fulfilled is not a function, promise2 should trigger completed with the value of promise1;
6.4. If onRejected is not a function, promise2 should trigger fulfilled with the reason of promise1
###7. resolvePromise
Promise 2: the return value of the current first promise;
10: Whether it is the execution result of onFulfilled or onRejected
resolve, reject: change method
resolvePromise(promise2, x, resolve, reject)
7.1 if promise2 and x are equal, reject typeError;
7.2 if x is a promise
If x is in the pending state, promise must be in the pending state until the state of X changes.
If x is fully, value -- > fulfilled
If x is rejected, reason -- > rejected
7.3 if x is an Object or Function
Go to get x.then(). If an error is reported, reject reason
If then is a function, then.call(x, resolvePromiseFn, rejectPromiseFn)
Why does changing the pointer of this or then with call cause the pointer to change, so use call to continue the previous logic
##5, Specific steps to realize promise (about nine steps);
###1. promise should be a constructor or class
const promise = new promise();
Create promise-class.js. Today we use class to implement promise
class MPromise{ constructor(){ } }
###2. Define three states
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ constructor(){ } }
###3. Initialization status
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ constructor(){ this.status = PENDING; this.value = null; this.reason = null; } }
###4. Implement resolve and reject
4.1 these two methods need to change the status from pending to fully / rejected
4.2 the input parameters are value and reason respectively
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ constructor(){ this.status = PENDING; this.value = null; this.reason = null; } resolve(value){ // The final state cannot be changed, so a judgment needs to be added // Only when the status is in the initial state can it be changed if(this.status === PENDING){ this.status = FULFILLED; this.value = value; } } reject(reason){ if(this.status === PENDING){ this.status = REJECTED; this.reason= reason; } } }
###5. Input parameter processing when instantiating promise
5.1 input parameter is a function
const promise = new promise((resolve,reject)=>{ })
5.2 accept the two parameters resolve and rejected
5.3 to initialize promise, this function should be executed synchronously, and any error should be thrown through reject
const promise = new promise((resolve,reject)=>{ axios.get('www.baidu.com') }).then(result=>{ // It should be noted that the creation place has been executed // Not when. then() is called // It is executed synchronously, so a promise can be cached, and the value can be obtained directly when needed // The request will not be sent again when obtaining, so there is no need to worry about traffic sneaking and performance problems })
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ constructor(fn){ this.status = PENDING; this.value = null; this.reason = null; //It needs to be called inside. If there is an error, it needs to be thrown immediately try{ // In consideration of preciseness, change this point to the current environment fn(this.resolve.bind(this), this.reject.bind(this)); } catch(e){ this.reject(e) } } resolve(value){ // The final state cannot be changed, so a judgment needs to be added // Only when the status is in the initial state can it be changed if(this.status === PENDING){ this.status = FULFILLED; this.value = value; } } reject(reason){ if(this.status === PENDING){ this.status = REJECTED; this.reason= reason; } } }
### 6. Implement then method
6.1 then accepts two parameters onFulfilled and onRejected
6.2 check and process parameters. If the parameter is not a function, ignore it
6.3 different functions need to be called according to the current promise state
If promise is full, we need to call onFulfilled
If the promise is rejected, we need to call onRejected
6.4 first get all callbacks, because when the status changes, we need to execute the corresponding callbacks whether it is successful or failed; Create two new arrays to store successful and failed callbacks respectively. When calling then, if it is still in pending state, it will be stored in the array.
6.5 when the status changes, execute the corresponding callback. Here, use the getter setter of es6 to listen for changes in status and perform corresponding operations when changes occur;
If ES5 does not have a getter setter, we can directly change the status in the resolve and reject methods; After using getter setter, you can maintain it better without paying attention to status;
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ //Just live two arrays directly. The arrays here will not be modified, but will be push ed in // Status completed list FULFILLED_CALLBACK_LIST = []; // list with failed status REJECTED_CALLBACK_LIST = []; // Store initialization status _status = PENDING; constructor(fn){ this.status = PENDING; this.value = null; this.reason = null; //It needs to be called inside. If there is an error, it needs to be thrown immediately try{ // In consideration of preciseness, change this point to the current environment fn(this.resolve.bind(this), this.reject.bind(this)); } catch(e){ this.reject(e) } } get status(){ // All real status return this._status; } set status(newStatus){ this._status = newStatus; // Judge different states and execute different logic switch(newStatus){ case FULFILLED: { // The then method has judged whether it is a function, so there is no need to judge here // When the status changes, execute the corresponding callback. this.FULFILLED_CALLBACK_LIST.forEach(callback=>{ callback(this.value) }); break; } case REJECTED: { this.REJECTED_CALLBACK_LIST.forEach(callback=>{ callback(this.reason) }); break; } } } resolve(value){ // The final state cannot be changed, so a judgment needs to be added // Only when the status is in the initial state can it be changed 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 fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) => value const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) => throw(reason) switch(this.status){ case FULFILLED: { fulFilledFn(this.value); break; } case REJECTED: { rejectedFn(this.reason); break; } case PENDING: { this.FULFILLED_CALLBACK_LIST.push(fulFilledFn); this.REJECTED_CALLBACK_LIST.push(rejectedFn); break; } } } isFunction(param){ return typeof param === 'function'; } }
### 7. Return value of then
7.1 if onFulfilled or onRejected throws an exception e, the new promise must reject e;
7.2 the return value should be a promise
7.3 if onFulfilled is not a function and promise1 is executed successfully (resolve status), promise2 must return the same status and value; (as defined in the specification)
7.4 if onRejected is not a function and promise1 refuses to execute, promise2 must return the same status and reason;
7.5 if onFulfilled or onRejected returns a value of x, run the resolvePromise method.
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ //Just live two arrays directly. The arrays here will not be modified, but will be push ed in // Status completed list FULFILLED_CALLBACK_LIST = []; // list with failed status REJECTED_CALLBACK_LIST = []; // Store initialization status _status = PENDING; constructor(fn){ this.status = PENDING; this.value = null; this.reason = null; //It needs to be called inside. If there is an error, it needs to be thrown immediately try{ // In consideration of preciseness, change this point to the current environment fn(this.resolve.bind(this), this.reject.bind(this)); } catch(e){ this.reject(e) } } get status(){ // All real status return this._status; } set status(newStatus){ this._status = newStatus; // Judge different states and execute different logic switch(newStatus){ case FULFILLED: { // The then method has judged whether it is a function, so there is no need to judge here // When the status changes, execute the corresponding callback. this.FULFILLED_CALLBACK_LIST.forEach(callback=>{ callback(this.value) }); break; } case REJECTED: { this.REJECTED_CALLBACK_LIST.forEach(callback=>{ callback(this.reason) }); break; } } } resolve(value){ // The final state cannot be changed, so a judgment needs to be added // Only when the status is in the initial state can it be changed 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 fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) => value const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) => throw(reason) //If onFulfilled or onRejected throws an exception e, the new promise must reject e; const fulFilledFnWitchCatch = (resolve, reject, newPromise) => { try{ // It is not a function that resolve s directly. Because there is a return value, it needs to be judged if(!this.isFunction(onFulfilled)){ resolve(this.value) }else{ const x = fulFilledFn(this.value); this.resolvePromise(newPromise, x, resolve, reject); } }catch(e) { reject(e) } } const rejectedFnWitchCatch = (resolve, reject, newPromise) => { try{ if(!this.isFunction(onRejected)){ reject(this.reason); }else{ const x = rejectedFn(this.reason); this.resolvePromise(newPromise, x, resolve, reject); } }catch(e) { reject(e) } } switch(this.status){ // The return value of then is a promise case FULFILLED: { const newPromise = new MPromise((resolve, reject) => fulFilledFnWitchCatch(resolve, reject, newPromise)); return newPromise; } case REJECTED: { const newPromise = new MPromise((resolve, reject) => rejectedFnWitchCatch(resolve, reject, newPromise)); return newPromise; } case PENDING: { const newPromise = new MPromise((resolve, reject) => { this.FULFILLED_CALLBACK_LIST.push(() => fulFilledFnWitchCatch(resolve, reject, newPromise)); this.REJECTED_CALLBACK_LIST.push(() => rejectedFnWitchCatch(resolve, reject, newPromise)); }); return newPromise; } } } // resolvePromise defined in the specification needs to accept a newPromise // The resolvePromise function is used to handle promise values // Allow promise to return a result, whether it is resolve or reject resolvePromise(newPromise, x, resolve, reject){ } isFunction(param){ return typeof param === 'function'; } }
###8. Specific implementation of resolvepromise method
The resolvePromise function is used to handle promise values, Allow promise to return a result, whether it is resolve or reject
8.1 if promise2 and x are equal
8.1 if x is a promise, promise must be in pending state until the state of X changes
If x fulfilled, value -- > fulfilled
If x rejected, reason ---- > rejected
8.1 if x is an object / Function
To get const then = x.then, reject reason
Then is a function, then.call(x,resolvePromiseFn, rejectPromiseFn)
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ //Just live two arrays directly. The arrays here will not be modified, but will be push ed in // Status completed list FULFILLED_CALLBACK_LIST = []; // list with failed status REJECTED_CALLBACK_LIST = []; // Store initialization status _status = PENDING; constructor(fn){ this.status = PENDING; this.value = null; this.reason = null; //It needs to be called inside. If there is an error, it needs to be thrown immediately try{ // In consideration of preciseness, change this point to the current environment fn(this.resolve.bind(this), this.reject.bind(this)); } catch(e){ this.reject(e) } } get status(){ // All real status return this._status; } set status(newStatus){ this._status = newStatus; // Judge different states and execute different logic switch(newStatus){ case FULFILLED: { // The then method has judged whether it is a function, so there is no need to judge here // When the status changes, execute the corresponding callback. this.FULFILLED_CALLBACK_LIST.forEach(callback=>{ callback(this.value) }); break; } case REJECTED: { this.REJECTED_CALLBACK_LIST.forEach(callback=>{ callback(this.reason) }); break; } } } resolve(value){ // The final state cannot be changed, so a judgment needs to be added // Only when the status is in the initial state can it be changed 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 fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) => value const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) => throw(reason) //If onFulfilled or onRejected throws an exception e, the new promise must reject e; const fulFilledFnWitchCatch = (resolve, reject, newPromise) => { try{ // It is not a function that resolve s directly. Because there is a return value, it needs to be judged if(!this.isFunction(onFulfilled)){ resolve(this.value) }else{ const x = fulFilledFn(this.value); this.resolvePromise(newPromise, x, resolve, reject); } }catch(e) { reject(e) } } const rejectedFnWitchCatch = (resolve, reject, newPromise) => { try{ if(!this.isFunction(onRejected)){ reject(this.reason); }else{ const x = rejectedFn(this.reason); this.resolvePromise(newPromise, x, resolve, reject); } }catch(e) { reject(e) } } switch(this.status){ // The return value of then is a promise case FULFILLED: { const newPromise = new MPromise((resolve, reject) => fulFilledFnWitchCatch(resolve, reject, newPromise)); return newPromise; } case REJECTED: { const newPromise = new MPromise((resolve, reject) => rejectedFnWitchCatch(resolve, reject, newPromise)); return newPromise; } case PENDING: { const newPromise = new MPromise((resolve, reject) => { this.FULFILLED_CALLBACK_LIST.push(() => fulFilledFnWitchCatch(resolve, reject, newPromise)); this.REJECTED_CALLBACK_LIST.push(() => rejectedFnWitchCatch(resolve, reject, newPromise)); }); return newPromise; } } } // resolvePromise defined in the specification needs to accept a newPromise // The resolvePromise function is used to handle promise values // Allow promise to return a result, whether it is resolve or reject resolvePromise(newPromise, x, resolve, reject){ if(newPromise === x){ // An error message is returned. The message doesn't matter. Anything can be // Why reject an error message? If newPromise and x are equal, they will call each other to form an endless loop return reject(new TypeError('Type Error,Please....')) } if(x instanceOf MPromise){ //If it's promise, there must be a then method x.then(y =>{ this.resolvePromise(newPromise, y, resolve, reject) }, reject); } else if(typeof x === 'object' || this.isFunction(x)){ // typeof null is also an object, so it needs to be judged if(x === null){ return resolve(x) } // According to the standard semantic writing method let then = null; try{ then = x.then; }catch(error){ return reject(error); } if(this.isFunction(then)){ // The specification requires that the then method can only be called once // Define a called variable to identify whether it is called let called = false; try{ // In order to avoid abnormal errors, replace this point of then with x then.call( x, (y) =>{ if(called){ return; } called = true; // Simple recursion, the purpose is to find all x this.resolvePromise(newPromise, y, resolve, reject); }, (r) =>{ if(called){ return; } called = true; reject(r); } ) }catch(error){ if(called){ return; } reject(error); } }else{ resolve(x); } } else { resolve(x) } } isFunction(param){ return typeof param === 'function'; } }
###9. onFulfilled and onRejected are executed in micro tasks
How?
queueMicrotask(()=>{}); Pass in a function and call it in the micro task
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; class MPromise{ //Just live two arrays directly. The arrays here will not be modified, but will be push ed in // Status completed list FULFILLED_CALLBACK_LIST = []; // list with failed status REJECTED_CALLBACK_LIST = []; // Store initialization status _status = PENDING; constructor(fn){ this.status = PENDING; this.value = null; this.reason = null; //It needs to be called inside. If there is an error, it needs to be thrown immediately try{ // In consideration of preciseness, change this point to the current environment fn(this.resolve.bind(this), this.reject.bind(this)); } catch(e){ this.reject(e) } } get status(){ // All real status return this._status; } set status(newStatus){ this._status = newStatus; // Judge different states and execute different logic switch(newStatus){ case FULFILLED: { // The then method has judged whether it is a function, so there is no need to judge here // When the status changes, execute the corresponding callback. this.FULFILLED_CALLBACK_LIST.forEach(callback=>{ callback(this.value) }); break; } case REJECTED: { this.REJECTED_CALLBACK_LIST.forEach(callback=>{ callback(this.reason) }); break; } } } resolve(value){ // The final state cannot be changed, so a judgment needs to be added // Only when the status is in the initial state can it be changed 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 fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) => value const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) => throw(reason) //If onFulfilled or onRejected throws an exception e, the new promise must reject e; const fulFilledFnWitchCatch = (resolve, reject, newPromise) => { queueMicrotask(() => { try{ // It is not a function that resolve s directly. Because there is a return value, it needs to be judged if(!this.isFunction(onFulfilled)){ resolve(this.value) }else{ const x = fulFilledFn(this.value); this.resolvePromise(newPromise, x, resolve, reject); } }catch(e) { reject(e) } }); } const rejectedFnWitchCatch = (resolve, reject, newPromise) => { queueMicrotask(() => { try{ if(!this.isFunction(onRejected)){ reject(this.reason); }else{ const x = rejectedFn(this.reason); this.resolvePromise(newPromise, x, resolve, reject); } }catch(e) { reject(e) } }); } switch(this.status){ // The return value of then is a promise case FULFILLED: { const newPromise = new MPromise((resolve, reject) => fulFilledFnWitchCatch(resolve, reject, newPromise)); return newPromise; } case REJECTED: { const newPromise = new MPromise((resolve, reject) => rejectedFnWitchCatch(resolve, reject, newPromise)); return newPromise; } case PENDING: { const newPromise = new MPromise((resolve, reject) => { this.FULFILLED_CALLBACK_LIST.push(() => fulFilledFnWitchCatch(resolve, reject, newPromise)); this.REJECTED_CALLBACK_LIST.push(() => rejectedFnWitchCatch(resolve, reject, newPromise)); }); return newPromise; } } } catch(onRejected){ return this.then(null, onRejected) } // resolvePromise defined in the specification needs to accept a newPromise // The resolvePromise function is used to handle promise values // Allow promise to return a result, whether it is resolve or reject resolvePromise(newPromise, x, resolve, reject){ if(newPromise === x){ // An error message is returned. The message doesn't matter. Anything can be // Why reject an error message? If newPromise and x are equal, they will call each other to form an endless loop return reject(new TypeError('Type Error,Please....')) } if(x instanceOf MPromise){ //If it's promise, there must be a then method x.then(y =>{ this.resolvePromise(newPromise, y, resolve, reject) }, reject); } else if(typeof x === 'object' || this.isFunction(x)){ // typeof null is also an object, so it needs to be judged if(x === null){ return resolve(x) } // According to the standard semantic writing method let then = null; try{ then = x.then; }catch(error){ return reject(error); } if(this.isFunction(then)){ // The specification requires that the then method can only be called once // Define a called variable to identify whether it is called let called = false; try{ // In order to avoid abnormal errors, replace this point of then with x then.call( x, (y) =>{ if(called){ return; } called = true; // Simple recursion, the purpose is to find all x this.resolvePromise(newPromise, y, resolve, reject); }, (r) =>{ if(called){ return; } called = true; reject(r); } ) }catch(error){ if(called){ return; } reject(error); } }else{ resolve(x); } } else { resolve(x) } } isFunction(param){ return typeof param === 'function'; } }
Here, our simple promise has been implemented and can be simply tested
const test = new MPromise((resolve, reject) => { setTimeout(()=>{ resolve(1111); },1000) }).then(console.log) const test = new MPromise((resolve, reject) => { setTimeout(()=>{ reject(1111); },1000) }).then((value) => { console.log('complete' + value) }).catch((reason) => { console.log('report errors' + reason) })