If the article is helpful to you, your star is the best encouragement for me ~
In a nutshell: Promises allow us to solve the “callback hell” problem by chain-calling, especially in asynchronous processes, and Promise to keep code clean and readable. This paper mainly interprets the Promise/A+ specification, and implements A Promise on the basis of this specification.
First, the use of promises
Before we learned about the Promise specification, we knew that Promise was already supported in ECMA by the major older browsers.
Create an instance of Promise:
var p=new Promise(function(resolve,reject){
setTimeout(function(){
resolve("success")
},1000);
console.log("Create a new Promise");
})
p.then(function(x){console.log(x)}) // Output: create a new Promise successCopy the code
Here is an example of a promise, and the output reads, “Create a Promise”, and after a 1000ms delay, “success” is printed.
As you can see from the examples above, promises are convenient for handling asynchronous operations. In addition, promise can be invoked in chained form:
var p=new Promise(function(resolve,reject){resolve()}); p.then(...) .then(...) .then(...)Copy the code
In addition to the then method, Promise also provides promise. resolve, promise. all, promise. race and so on.
Ii. Promise/A+ specification
The Promise/A+ specification expands on earlier Promise/A proposal proposals, so let’s take A look at the Promise/A+ specification.
1. The term
(1) a “promise” is an object or function that has a then method
(2) “thenable” is an object or function that defines the then method
(3) “value” is the value when the promise state succeeds
(4) “reason” is the value when the promise state fails
The purpose of specifying terms is to keep the code normative as we implement promises ourselves (you can skip this section too).
2. Request
(1) A promise must have three states: pending, fulfilled(resolved), and when it is in the pending state, it can be moved to the fulfilled(resolved) or rejected state. This will be a pity (resolved) state or the rejected state.
A promise is a promise, which means that once the state of a promise changes, it is forever irreversible.
(2) A promise must have a THEN method that takes two arguments:
promise.then(onFulfilled,onRejected)
Copy the code
Where onFulfilled method represents the method executed when the state is fulfilled from pending — >fulfilled(resolved), and onRejected represents the method executed when the state is fulfilled from pending — > Fulfilled (resolved).
(3) In order to implement chained calls, the then method must return a promise
promise2=promise1.then(onFulfilled,onRejected)
Copy the code
Implement A Promise that conforms to the Promise/A+ specification
Now that we’ve read the Promise/A+ specification, let’s look at how to implement A Promise by first constructing A myPromise function that, for all variables and function names, should remain the same as in the specification.
1. V1.0 initial version myPromise
function myPromise(constructor){
let self=this;
self.status="pending"Self. value=undefined; Self. reason=undefined; // Define the state of the rejected statefunctionResolve (value){// Two ==="pending", ensuring that the change in state is irreversibleif(self.status==="pending"){
self.value=value;
self.status="resolved"; }}functionReject (reason){// Two === ="pending", ensuring that the change in state is irreversibleif(self.status==="pending"){
self.reason=reason;
self.status="rejected"; Constructor (resolve,reject); }catch(e){ reject(e); }}Copy the code
Also, you need to define the then method of the chain call on the myPromise prototype:
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
Copy the code
This is an initial version of myPromise, where the state changes and then the corresponding then method can perform different actions depending on the state.
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)}) // Output 1Copy the code
But here myPromise cannot handle asynchronous resolve. For example:
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});
p.then(function(x){console.log(x)}) // No outputCopy the code
2. V2.0 is implemented based on observation mode
To handle asynchronous resolve, we change the myPromise definition to use two arrays onFullfilledArray and onRejectedArray to hold asynchronous methods. Executes the methods in the array once when the state changes.
function myPromise(constructor){
let self=this;
self.status="pending"Self. value=undefined; Self. reason=undefined; Self. OnFullfilledArray =[]; self.onRejectedArray=[];function resolve(value){
if(self.status==="pending"){
self.value=value;
self.status="resolved";
self.onFullfilledArray.forEach(function(f){ f(self.value); // If the state changes from pending to Resolved, // iterate through the async methods}); }}function reject(reason){
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
self.onRejectedArray.forEach(function(f){ f(self.reason); // If the state changes from pending to rejected, // then run through the asynchronous method in the execution})}} // catch the constructor exception try{constructor(resolve,reject); }catch(e){ reject(e); }}Copy the code
For then methods, when the state is pending, add methods to the array:
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "pending":
self.onFullfilledArray.push(function(){
onFullfilled(self.value)
});
self.onRejectedArray.push(function(){
onRejected(self.reason)
});
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
Copy the code
This way, by using two arrays and starting execution after a state change, can handle the problem of asynchronous resolve not being called. This version of myPromise can handle all asynchrony, so is that complete?
No, the biggest feature of the Promise/A+ specification is the chain call, which means that the then method should return A Promise.
3. V3.0 then method to achieve chain call
To realize the chain call through then method, that is to say, then method needs to return a primise every time it is called, and at the same time, add an error handling part in the constructor that returns promise. Let’s transform then method
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
let promise2;
switch(self.status){
case "pending":
promise2=new myPromise(function(resolve,reject){
self.onFullfilledArray.push(function(){
try{
let temple=onFullfilled(self.value);
resolve(temple)
}catch(e){
reject(e) //error catch
}
});
self.onRejectedArray.push(function(){
try{
let temple=onRejected(self.reason);
reject(temple)
}catch(e){
reject(e)// error catch
}
});
})
case "resolved":
promise2=new myPromise(function(resolve,reject){
try{
lettemple=onFullfilled(self.value); // Will be the last onethenThe method inside passes into the next Promise state resolve(temple); }catch(e){ reject(e); //error catch } })break;
case "rejected":
promise2=new myPromise(function(resolve,reject){
try{
lettemple=onRejected(self.reason); / / will bethenResolve (temple) is passed to the next Promise state; }catch(e){ reject(e); }})break;
default:
}
return promise2;
}
Copy the code
This allows the chained call to be made by returning a promise via the then method:
p.then(function(x){console.log(x)}).then(function(){console.log("Chain call 1")}).then(function(){console.log("Chain call 2"}) // Output 1 chain call 1 chain call 2Copy the code
In the Promise/A+ specification, the onFullfilled () and onRejected () methods can return an object, A function, or even another Promise.
V4.0 then onFullfilled and onRejected (
In particular, the return value of the onFullfilled and onRejected methods may be a promise.
(1) First look at the promise for the return value of the onFullfilled function
I) If the onFullfilled function returns the promise itself, a type error will be thrown
If the onFullfilled function returns a different promise, execute the promise’s then function, which transfers the state of the promise to the new promise. III) If the return is a nested promsie, So we need recursion.
IV) If a non-PROMsie object or function is returned, it will choose to give that object or function directly to the new Promise.
The resolve function will be redefined as Promise/A+ : The resolvePromise function, which takes the current promise, the onFullfilled function, or the return value of the onRejected function, resolve, and reject.
The resolvePromise function is defined as follows:
function resolvePromise(promise,x,resolve,reject){
if(promise===x){
throw new TypeError("type error")}let isUsed;
if(x! ==null&&(typeof x==="object"||typeof x==="function")){
try{
let then=x.then;
if(typeof then= = ="function"){// is a promise case thene.call (x,function(y){
if(isUsed)return;
isUsed=true;
resolvePromise(promise,y,resolve,reject);
},function(e){
if(isUsed)return;
isUsed=true; reject(e); })}elseResolve (x)}}catch(e){// just a function or object.if(isUsed)return;
isUsed=true; reject(e); }}elseResolve resolve(x)}}Copy the code
After changing the resolvePromise function, our calls to the then method become resolvePromises instead of promises.
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
let promise2;
switch(self.status){
case "pending":
promise2=new myPromise(function(resolve,reject){
self.onFullfilledArray.push(function() {setTimeout(function(){
try{
let temple=onFullfilled(self.value);
resolvePromise(temple)
}catch(e){
reject(e) //error catch
}
})
});
self.onRejectedArray.push(function() {setTimeout(function(){
try{
let temple=onRejected(self.reason);
resolvePromise(temple)
}catch(e){
reject(e)// error catch
}
})
});
})
case "resolved":
promise2=new myPromise(function(resolve,reject){
setTimeout(function(){
try{
lettemple=onFullfilled(self.value); // Will be the last onethenThe method inside passes to the next Promise state resolvePromise(temple); }catch(e){ reject(e); //error catch } }) })break;
case "rejected":
promise2=new myPromise(function(resolve,reject){
setTimeout(function(){
try{
lettemple=onRejected(self.reason); / / will bethenThe method inside is passed to the state of the next Promise, resolvePromise(temple); }catch(e){ reject(e); }})})break;
default:
}
return promise2;
}
Copy the code
So this will handle all the onFullfilled return values.
var p=new Promise(function(resolve,reject){resolve("Initialize promise")})
p.then(function() {return new Promise(function(resolve,reject){resolve("Then the promise returns the value")})}).then(function{the console. The log (x) (x)}) / / outputthenThe return value from promiseCopy the code
It might be a little confusing here, but let’s get it straight. First we return two values:
-
The return value of the then function — > returns a new promise, thus implementing the chain call
-
The onFullfilled and onRejected methods in the then function — > return the base value or a new promise
The return value of the onFullfilled method determines the return value of the then function.
Iv. Test whether the Promise/A+ specification is fully met
npm install -g promises-aplus-tests
Copy the code
See Promise test for more details
promises-aplus-tests myPromise.js
Copy the code
The result is:
The address of the complete code
https://github.com/forthealllight/promise-achieve
Add the Typescript implementation Promise/A+ specification (skip this section)
interface IConstructor{ (resolve:IResolve,reject:IReject):void } interface IResolve { (x:any):void } interface IReject { (x:any):void }function myPromise(constructor:IConstructor):void{
let self:object=this;
self.status="pending"; self.value=undefined; //ifpending->resolved self.reason=undefined; //ifpending->rejected self.onFullfilledArray=[]; //to deal with async(resolved) self.onRejectedArray=[]; //to deal with async(rejeced)let resolve:IResolve;
resolve=function(value:any):void{
//pending->resolved
if(self.status==="pending"){
self.status="resolved";
self.value=value;
self.onFullfilledArray.forEach(function(f){ f(self.value); }}})let reject:IReject;
reject=function(reason:any):void{
if(self.status==="pending"){
self.status="rejected";
self.reason=reason;
self.onRejectedArray.forEach(function(f){
f(self.reason);
})
}
}
//According to the definition that the function "constructor"accept two parameters //error catch try { constructor(resolve,reject); } catch (e) { reject(e); }}Copy the code
Simply write a utility function, using TS is still a bit of a readability problem.