Daily study notes, including ES6, Promise, Node.js, Webpack, HTTP principles, Vue family buckets, and possibly more Typescript, Vue3, common interview questions, etc.
Promise
References Promise | MDN
Why Promise occurs: Handling multiple concurrent requests solves the problem of callback hell with chained calls.
A Promise has three states: resolve, reject, and Pending.
First, Promise is a class that needs to be instantiated with the keyword new.
A Promise accepts an executor function as an executor, which executes immediately. It also accepts two parameters as a callback for success and failure.
When we do not execute a success or failure callback, the current Promise state remains in the wait state. The Promise class returns a Promise class for the next call.
let promise = new Promise((resolve, reject) = >{})
console.log(promise) // Promise {<pending>}
Copy the code
The return value of the Promise instance will determine whether the current success or failure status is returned based on the function called, and will return the passed arguments. When the function is called, undefined is returned if no arguments are passed.
// Nothing
let promise = new Promise((resolve, reject) = > {
resolve()
})
console.log(promise); // Promise { undefined }
// Success status
let promise = new Promise((resolve, reject) = > {
resolve('success')})console.log(promise); // Promise { 'success' }
// Failed
let promise = new Promise((resolve, reject) = > {
reject('failed')})console.log(promise); // Promise { <rejected> 'failed' }
Copy the code
On each instance of a Promise, there is a.then method that prints the result passed in by the previous instance. Once the current instance state has been changed, it cannot be changed.
let promise = new Promise((resolve, reject) = > {
resolve('success')
}).then((result) = > {
console.log(result); // success
}, (error) = > {
console.log(error);
})
Copy the code
In this case, we can summarize several characteristics of promises.
The characteristics of
- PromiseIs a class, and there are no compatibility issues.
- PromiseIt passes in a function (
executor
) as an executor, this executor executes immediately. executor
Provides two functions (resolve
和reject
) is used to describe the currentPromiseWhile the current instance has three states,The successful state 、 The failure state 和 Wait state, the current instance defaults toWait state. If the callresolve
Then the state becomesThe successful state, the callreject
Or an exception occursThe failure state 。- PromiseOnce the state has changed, it cannot be changed.
- eachPromiseThere’s one for each instance
.then
Methods.
We can write a set of promises by hand based on a few characteristics of promises.
Handwriting Promises/A+ specifications
Document specification Promises/A+
Note: The code content is continuous, please watch in order. thank you
Promise’s base features
According to the above characteristics, we can simply achieve the Promise effect.
const PEDDING = 'PEDDING'; // Wait state
const FULFILLED = 'FULFILLED'; // Success status
const REJECTED = 'REJECTED'; // Failed
class Promise {
constructor(executor) {
this.status = PEDDING; // Default state
this.result = undefined; // Successful callback
this.reason = undefined; // Failed callback
const resolve = (result) = > { // The resolve function succeeds
if (this.status === PEDDING) {
this.status = FULFILLED; // Modify the status
this.result = result; // Add a callback}}const reject = (reason) = > { // Reject fails
if (this.status === PEDDING) {
this.status = REJECTED; // Modify the status
this.reason = reason; // Add a callback}}try {
executor(resolve, reject)
} catch (error) {
this.reason = error; }}then(onFulfilled, onRejected) {
if (this.status === FULFILLED) { // The method to call on success
onFulfilled(this.result)
}
if (this.status === REJECTED) { // Method called on failure
onRejected(this.reason)
}
}
}
module.exports = Promise
Copy the code
By referring to the Promise A+ specification, we can simply implement A simple implementation version of the Promise class.
Implement the Promise’s asynchronous functionality
To make promises async, we need to make it clear that promises are async only when they trigger.then methods (resolve and reject). So we use this idea.
When the user calls the.then method, the Promise may be in a wait state and we need to temporarily save it. Later calls to resolve and Reject trigger the corresponding onFulfilled and onRejected
Based on the description above, we can capture the two keywords staging and triggering, so we can use the publish-and-subscribe design pattern to achieve this functionality.
// ...
class Promise {
constructor(executor) {
// ...
this.onResolveCallbacks = []; // To store successful callbacks
this.onRejectCallbacks = []; // To store the failed callback
const resolve = (result) = > {
if (this.status === PEDDING) {
// ...
this.onResolveCallbacks.forEach(fn= > fn())
}
}
const reject = (reason) = > {
if (this.status === PEDDING) {
// ...
this.onRejectCallbacks.forEach(fn= > fn())
}
}
// ...
}
then(onFulfilled, onRejected) {
if (this.status === PEDDING) {
this.onResolveCallbacks.push(() = > {
onFulfilled(this.result)
})
this.onRejectCallbacks.push(() = > {
onRejected(this.reason)
})
}
// ...}}module.exports = Promise
Copy the code
Create two arrays to store the callback functions. First store the functions that need to be executed into the array. When the asynchronous execution is complete, the functions stored in the array are executed in turn.
Promise chain calls
First of all, what problems did Promise solve?
- Handle multiple concurrent requests
- Chained calls solve the problem of callback hell
What is callback hell? Callback hell is when we normally deal with business code, the API parameters of the next interface need to use the parameters of the last interface. Multiple levels of nesting can occur in the code, making it difficult to read.
Here we need to use a chained call to the Promise, which is a loop call to the.then method, which returns a new Promise when called.
Let’s first encapsulate a Promise asynchronous function
let fs = require('fs');
function readFile(path, encoding) {
return new Promise((resolve, reject) = > {
fs.readFile(path, encoding, (err, data) = > {
if (err) reject(err)
resolve(data)
})
})
}
Copy the code
Now we need to be aware of several situations in which chained calls occur.
-
If the.then method returns an ordinary value (not a Promise), it will be the successful result of the next outer.then method.
readFile('./a.txt'.'utf8').then((result) = > { return 1; }, (err) = > { console.log(err); }).then((result) = > { console.log(result); / / 1 }, (err) = > { console.log(err); }) Copy the code
-
If the.then method fails, it will go to the outer layer of the next failure result of the.then method.
readFile('./a.txt'.'utf8').then((result) = > { throw new Error('error')},(err) = > { console.log(err); }).then((result) = > { console.log(result); }, (err) = > { console.log(err); // Error: error }) Copy the code
(Note: Execution error required
throw new Error()
If used directlyreturn new Error()
, belong to return oneThe Error object, will execute the next successful result) -
The successful result of the.then method is executed the next time, as long as normal values are returned, regardless of whether the last execution of the.then method succeeded or failed.
If the path is incorrectly entered, the Promise defaults to the error result of the first layer. Then method and returns undefined. Then the execution result of the next layer is the success result and the value is undefined
// The entered path is incorrect readFile('./a.txt1'.'utf8').then((result) = > { console.log(result) }, (err) = > { // Return undefined here console.log(err); // Error cause }).then((result) = > { console.log(result); // undefined }, (err) = > { console.log(err); }) Copy the code
-
If the.then method returns a Promise object, the result of the Promise is treated as a success or failure * (success or failure is passed in).
readFile(`${bathPath}a.txt`.'utf8').then((result) = > { return readFile(`${bathPath}${result}`.'utf8')},(err) = > { console.log('err1', err); }).then((result) = > { console.log('success2', result); // success2 b }, (err) = > { console.log('err2', err); // error }) Copy the code
Conclusion: If the return is a normal value (not a Promise), it is passed to the next time.then
The success of the method. If it returns a failed Promise or throws an exception, it is passed to the next time.then
Method failure.
Hand-written implementation promise chained calls
Depending on the characteristics and circumstances described above, we will return a new Promise instance every time after the.then method is called. So we can modify the.then method we wrote earlier.
Let’s first deal with the normal value (not the Promise) case.
(Note: here we separate out an X for subsequent processing)
// Rewrite the.then() method
then(onFulfilled, onRejected) {
let promise = new Promise((resolve, reject) = > { // Return a Promise instance
if (this.status === FULFILLED) {
try {
let x = onFulfilled(this.result)
resolve(x);
} catch (e) {
reject(e)
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
}
if (this.status === PEDDING) {
this.onResolveCallbacks.push(() = > {
try {
let x = onFulfilled(this.result)
resolve(x);
} catch (e) {
reject(e)
}
})
this.onRejectCallbacks.push(() = > {
try {
let x = onRejected(this.reason)
resolve(x)
} catch (e) {
reject(e)
}
})
}
})
return promise
}
Copy the code
This idea is used to modify the previous method so that we can work with ordinary values.
We can modify the above case for ordinary values to handle more cases. To do this we need to encapsulate a resolvePromise() function to do the processing.
ResolvePromise () takes four arguments: the current instance promise, the result X, the success callback resolve, and the failure callback reject.
In order to pass the current strength promise as an argument, we need to first encapsulate it with the asynchronous method setTimeout (or any other method).
then(onFulfilled, onRejected) {
let promise = new Promise((resolve, reject) = > { // Return a Promise instance
if (this.status === FULFILLED) {
setTimeout(() = > {
try {
let x = onFulfilled(this.result)
// This is where the package is processed
resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e)
}
}
}
/ /... Do the same in the following code
})
return promise
}
Copy the code
Now that we can read the Promise instance, let’s implement the resolvePromise() function.
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError('wrong'))}// Promise compatibility
if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
try {
let then = x.then // When implemented with defineProperty, there may be an exception in the value
if (typeof then === 'function') {
then.call(x, y= > {
resolve(y)
}, r= > {
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
/ / common values
resolve(x)
}
}
Copy the code
(Note: At work, we may call promises that are encapsulated by others, and there may be problems with them. So we need to do one more step, which is to put a lock in the code to ensure that the code is strict.
function resolvePromise(promise, x, resolve, reject) {
// ...
if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
let called = false; // Define a parameter
try {
let then = x.then // When implemented with defineProperty, there may be an exception in the value
if (typeof then === 'function') {
then.call(x, y= > {
// Make an exception judgment here
if (called) return
called = true
resolve(y)
}, r= > {
if (called) return
called = true
reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// ...}}Copy the code
Thus we implement a chained call to a Promise.
Special case handling
Nested Promise
There may be another case where we pass a Promise instance in the resolve of the.then method. How do we handle this case?
The following situation
let promise = new Promise((resolve, reject) = > {
resolve(1)
}).then(() = > {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve(new Promise((resolve, reject) = > {
setTimeout(() = > {
resolve(200)},1000); }})),1000);
})
})
promise.then(result= > {
console.log(result);
}, err= > {
console.log(err);
})
Copy the code
In this particular case, we need to continue to modify the previous resolvePromise() function.
function resolvePromise(promise, x, resolve, reject) {
// ...
if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
// ...
try {
// ...
if (typeof then === 'function') {
then.call(x, y= > {
// ...
// Keep parsing until it is not a Promise
resolvePromise(promise, y, resolve, reject)
}, r= > {
// ...})}else {
resolve(x)
}
} catch (e) {
// ...}}else {
// ...}}Copy the code
The key is to recurse the call until it has a normal value.
Parameters through
The following happens when we call the.then method
new Promise((resolve, reject) = > {
resolve(100)
// reject('err')
}).then().then().then().then(result= > {
console.log(result); / / 100
}, err= > {
console.log(err); // If passed, output err
})
Copy the code
With no parameters passed in, the result is passed until output.
In this case of parameter penetration, we also need to make changes in the code.
then(onFulfilled, onRejected) {
// This will be fulfilled
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v= > v;
onRejected = typeof onRejected === 'function' ? onRejected : err= > {throw err}; // The error will only be output if thrown, so throw
// ...
}
Copy the code
Promise to test
We can test our own encapsulated promises using the test package Promises -aplus-tests.
Execute the following code in the Promise instance directory
npm install promises-aplus-tests -g
promises-aplus-tests ./promise.js
Copy the code
He will automatically check whether the Promise we encapsulate meets the Promise A+ specification.
Add a delay object under the Promise file we encapsulated.
class Promise {
/ /... Self encapsulated Promise
}
// The code that needs to be tested
Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) = > {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
module.exports = Promise
Copy the code
(Note: Catch, all, etc., are not included in the Promise specification)
After the detection, we can see the output results, according to which we can know whether the Promise encapsulated can run normally.
At this point, we have encapsulated a Promise.
Delay object
To help us reduce one application, which is not very widely used. It’s kind of like an agency.
We can encapsulate our initial readFile read operation.
function readFile(path, encoding) {
let dfd = Promise.deferred();
fs.readFile(path, encoding, (err, data) = > {
if (err) return dfd.reject(err)
dfd.resolve(data)
})
return dfd.promise;
}
Copy the code
This article is created by Mo Xiaoshang, if there are any problems and flaws in the article, you are welcome to correct and communicate. You can also follow my personal site, blog garden and nuggets, and I will upload my posts to these platforms as soon as they are produced. Finally, thank you for your support!