Asynchronous development
We know that Promises were created to handle asynchronous operations in JS. Before promises, callbacks were used to handle asynchronous operations. Once asynchronous operations get a little more complicated, it’s easy to get into the fabled callback hell that makes code less readable and maintainable. For example, if we wanted to implement the following asynchronous operation, the callback would be executed as follows:
let fs = require('fs');
fs.readFile('a.txt', (err,data) => {
fs.readFile('b.txt', (err,data) => {
fs.readFile('c.txt', (err,data) => {
/ /...
});
});
});
Copy the code
With promises, you can write:
let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);
readFile('a.txt')
.then(data= > {
return readFile('b.txt');
}).then(data= > {
return readFile('c.txt');
}).then(data= > {
/ /...
});
Copy the code
Of course, we can also use ES7 async/await to write, which is easier:
let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);
async function read() {
await readFile('a.txt');
await readFile('b.txt');
await readFile('c.txt');
// ...
}
read()
Copy the code
By comparison, the async/await method is the most concise and almost synchronous code. However async/await also evolved from promise and can be seen as syntactic sugar based on promise. So let’s look at how to make a promise by hand.
Basic use of Promise code
let p = new Promise((resolve, reject) = > {
resolve(123)
})
p.then((data) = > {
console.log(data) // We can take the value of the argument passed in resolve
}, (err) => {
console.log('e', err)
})
Copy the code
Pass in an executor argument with new Promise()
class Promise {
constructor(executor) {
this.status = 'pending' // The default state is wait
this.value = undefined
this.reason = undefined
let resolve = value= > {
if (this.status === 'pending') {
this.value = value
this.status = 'resolved'}}let reject = reason= > {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejecte'}}try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// The return result of the promise is taken as an argument to the outer next THEN
// If return
// The then method calls and returns a new promise
then(onFufilled, onRjected) {
if (this.status === 'resolved') {
onFufilled(this.value)
}
if (this.status === 'rejected') {
onRjected(this.reason)
}
}
}
Copy the code
That’s the basic promise implementation, but it’s not enough because asynchronous actions in promises are not yet supported. Such as:
let p = new Promise((resolve, reject) = > {
// Asynchronous operations in promise
setTimeout((a)= > {
resolve(123)},1000)
})
p.then((data) = > {
console.log(data) // The resolve parameter is not available
}, (err) => {
console.log('e', err)
})
Copy the code
So when promise is pending, ondepressing and onRjected can be saved first when they are invoked by then method, and invoked when they transition to resolved or rejected. The idea of publish and subscribe is used here.
constructor(executor) {
// ...
this.onResolvedCallbacks = [] // Store the successful callback publish subscription
this.onRejectedCallbacks = [] // Store the failed callback
let resolve = value= > {
if (this.status === 'pending') {
// ...
this.onResolvedCallbacks.forEach(fn= > fn()) / / release}}let reject = reason= > {
if (this.status === 'pending') {
// ...
this.onRejectedCallbacks.forEach(fn= > fn())
}
}
// .
}
then(onFufilled, onRjected) {
// ...
if (this.status === 'pending') {
this.onResolvedCallbacks.push((a)= > { / / subscribe
onFufilled(this.value)
})
this.onRejectedCallbacks.push((a)= > { / / subscribe
onRjected(this.reason)
})
}
}
}
Copy the code
At this point, our Promise already supports asynchronous code. Next we need to implement the chain calls to the THEN method, and here’s the hard part. Then method has the following characteristics:
- The THEN method returns a new promise each time. If an explicit promise is returned, the promise is used as its return value (not the promise currently invoked by the THEN method). If a normal value is returned, a new promise must be manually generated and returned
- The THEN method is invoked asynchronously
- If an exception occurs in the THEN method, reject is called to jump to the onRejected of the next THEN
- If the callback parameter is not returned in then(), it can be considered to skip the current THEN method and proceed to the next THEN method, i.e. value penetration
function resolvePromise(promise2, x, resolve, reject) {
// Determine if x is a promise
if (promise2 === x) { // x is promise2
return reject(new TypeError('Circular reference'))}if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) { // x is a promise
let called // Avoid calling reject after resolve
try {
let then = x.then // take the then method of x
if (typeof then === 'function') { // If then is a function then is a promise
then.call(x, y => { // If y is a promise, continue recursively resolving the promise
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, err => {
if (called) return
called = true
reject(err)
})
}
} catch(e) {
if (called) return
called = true
reject(e)
}
} else { // Then is an ordinary object
resolve(x)
}
}
then(onFufilled, onRjected) {
// Resolve non-onfufilled onRjected issue, value penetration
onFufilled = typeof onFufilled === 'function' ? onFufilled : y= > y
onRjected = typeof onRjected === 'function' ? onRjected : err= > { throw err }
let promise2 // then returns a new promise
if (this.status === 'resolved') {
promise2 = new Promise((resolve, reject) = > {
// if x is a promise, it will be used as the success of promise2. If x is a normal value, it will be used as the success of promise2
console.log(promise2)
setTimeout((a)= > {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
reject(e)
}
}, 0)})//console.log(promise2)
// resolvePromise(promise2, x, resolve, reject)
}
if (this.status === 'rejected') {
promise2 = new Promise((resolve, reject) = > {
setTimeout((a)= > {
try {
let x = onRjected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
reject(e)
}
}, 0)})}if (this.status === 'pending') { // Async code in executor
promise2 = new Promise((resolve, reject) = > {
this.onResolvedCallbacks.push((a)= > { / / subscribe
setTimeout((a)= > {
try {
let x = onFufilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch(e) {
reject(e)
}
}, 0)})this.onRejectedCallbacks.push((a)= > {
setTimeout((a)= > {
try {
let x = onFufilled(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)})})}return promise2
}
Copy the code
The resolvePromise resolves the return value of the THEN method
p.then(data= > {
return new Promise((resolve,reject) = >{
//resolve is passed as a Promise
resolve(new Promise((resolve,reject) = >{
resolve(2);
}));
});
})
Copy the code