I’ve been writing front ends for a few years now and I’m looking at the promiseA+ specification and I want to write this classic function for front-end asynchronous flow control.
Let’s start with a simple example of how Promise works.
let myFirstPromise = new Promise(function(resolve, reject){// When asynchronous codesetResolve (...) is called only when Timeout is successful. Reject (...) is called when asynchronous code fails. // The success and failure states are manually controlled by ourselves // in this case, we usesetTimeout(...) To simulate asynchronous code, which could be an XHR request or some HTML5 API method.setTimeout(function(){
resolve("Success!"); // The code works fine! }, 250); }); myFirstPromise.then(function(successMessage){//successMessage values resolve(...) // The successMessage argument doesn't have to be a string, but as an example console.log("Yay! " + successMessage);
});
Copy the code
PromiseA + implements a promise of its own
Executor is a function that takes resolve and reject. The Promise constructor calls the executor function immediately upon execution, passing resolve and reject as arguments to the executor. When the resolve and reject functions are called, the state of the promise will be fulfilled or Rejected, respectively. There are usually some asynchronous operations performed internally by the executor. Once completed, you can call the resolve function to change the promise state to fulfilled or change its state to Rejected if an error occurs. If an error is thrown in the Executor function, the promise state is Rejected. The return value of the executor function is ignored.
var promise = new Promise(function(resolve, reject) {// Call resolve and value on success // call reject and reason on failure})Copy the code
The second step is to write the framework of the constructor
function Promise(executor) {
letSelf = this // Since the promise asynchronous callback method, there must be a wait state, a success state, and a failure state. self.status ='pending'Self. onResolvedCallback = [] self.onResolvedCallback = [] self.onResolvedCallback = [] Since more than one callback may be added to a Promise before it ends, self.onrejectedCallback = [] // The set of callback functions used in Promise reject, Executor (resolve, reject) // Execute executor and pass in arguments}Copy the code
Third, implement resolve and Reject and throw exceptions internally and immediately change promise to reject
function Promise(executor) {
let self = this
self.status = 'pending'Self. onResolvedCallback = [] self.onResolvedCallback = [] self.onResolvedCallback = [] Self.onrejectedcallback = [] // The set of callback functions that can be added to a Promise before it ends because more than one callback can be added to a Promise before it endsfunctionResolve (value) {//resolve (value)if (self.status === 'pending') {
self.status = 'resolved'Self. data = value // It is possible to call resolve multiple times, storing multiple callbacks into the success arrayfor(var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
}
functionReject (reason) {// reject, used laterif (self.status === 'pending') {
self.status = 'rejected'Self. data = reasonfor(var i = 0; i < self.onRejectedCallback.length; I ++) {self.onrejectedCallback [I](reason)}}} try {self.onrejectedCallback [I](reason)}}} Discard the Promise executor(resolve, reject) // execute executor} catch(e) {reject reject(e)}}Copy the code
Promise’s then method (solves onion syntax)
The native Promise object has a then method that implements callbacks after the Promise state is determined. This THEN method needs to be written on the prototype chain. The then method returns a new Promise, which implements the chained invocation, avoiding the onion structure of previous callback callbacks and making the program easier to understand.
Promise.prototype.then = function(onResolved, onRejected) {
let self = this
letPromise2 // according to promiseA+, ifthenThe argument is notfunction, we need to ignore it and handle onResolved = typeof onResolved == here'function' ? onResolved : function(v) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
})
}
if (self.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
})
}
}
Copy the code
Because the THEN method returns a new promise, we need to implement onResolved or onRejected in the THEN and determine the result of the new promise based on the return value. If onResolved/onRejected returns a Promise, promise2 will take the Promise directly: look at the code
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}
if (self.status === 'resolved'{// If the promise state is resolved and is resolved, we call onResolved // because it is possible to throw an exception, we package it in a try/catch blockreturn promise2 = new Promise(function(resolve, reject) {
try {
var x = onResolved(self.data)
if(x instanceof Promise) {// If onResolved returns a Promise object, take its outcome as promise2. Resolve (resolve, reject)} resolve(x) // otherwise, use its return value as the result of promise2} catch (e) {reject(e) // Use captured errors as the result of promise2}})} // same logic as resolveif (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
}
if (self.status === 'pending') {// As the Promise has a pending state, we cannot determine onResolved or onRejected. We need to determine the state of the Promise to proceed with the next processingreturn promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(self.data)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
})
}
}
Copy the code
So we’ve implemented a simple promise, which is basically the same as the official promise, except that there’s a problem if you call multiple THEN’s but don’t return a value, like this
New Promise(resolve=>resolve(" hehehehe ")).then().then().then().then().function foo(value) {
alert(value)
})
Copy the code
If only the return value is present on the last THEN, we need to pass the promise return value directly to the last THEN. Here, we do a value pass, and if there is no return value, we cast resolve down.
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}
Copy the code
Finally, when I look at the promiseA+ specification, I find that the callbacks of THEN need to be executed asynchronously. I haven’t thought about the reason carefully. After looking up some data on the Internet, I think that the main reason for asynchronous execution of promises is to prevent stack overflow and maintain consistency of execution. The promiseA+ standard returns a new Promise to the jobQuene JS runtime and there are some differences between jobQuene and eventLoop handling. The difference is that each JavaScript Runtime can have multiple Job queues, but only one Event Loop Queue. Execute all of the Job queues in this task, and then process the Event Loop Queue. So here we simulate jobQuene by putting the Promise callback in setTimeout to make sure that the callback executes asynchronously
function Promise(executor) {
var self = this
self.status = 'pending'
self.onResolvedCallback = []
self.onRejectedCallback = []
function resolve(value) {
if (value instanceof Promise) {
returnValue. Then (resolve, reject)} // Async callback functionsetTimeout(function() {
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
for (var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
})
}
functionReject (reason) {// Async callback functionsetTimeout(function() {
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
for (var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
})
}
try {
executor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise! '))}if (x instanceof Promise) {
if (x.status === 'pending') { //because x could resolved by a Promise Object
x.then(function(v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
x.then(resolve, reject)
}
return
}
if((x ! == null) && ((typeof x ==='object') || (typeof x === 'function'))) {
try {
then = x.then //because x.then could be a getter
if (typeof then= = ='function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
return v
}
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
throw r
}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {// implement onResolved try {var x = onResolved(self.data) resolvePromise(promise2, x, resolve, reject) } catch (reason) { reject(reason) } }) }) }if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {// Execute onRejected asynchronouslysetTimeout(function() {
try {
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(value)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
})
}
}
Copy the code
At this point, an implementation of a promise is written, followed by method implementations of the Promise All and the Promise Race
all
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function(results) { console.log(results); }); // Result [1, 2, 3]Copy the code
The resolve method is executed only if all promises have returned
Promise.all = function(arr){
return new Promise((resolve,reject)=>{
let list=[];
arr.forEach((item)=>{
item.then((data)=>{
list.push(data);
if(arr.length == list.length){ resolve(resolvList); } },(reason)=>{ reject(reason); })})})}Copy the code
Race returns as soon as it has one success.
var p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
Promise.race([p1, p2, p3]).then(function(value) { console.log(value); / / 1}); 1 / / resultsCopy the code
Promise.all = function(arr){
return new Promise((resolve,reject)=>{
letlist=[]; arr.forEach((item)=>{ item.then((data)=>{ resolve(resolvList); },(reason)=>{ reject(reason); })})})}Copy the code