Writing in the front
Javascript asynchronous programming goes through four phases: Callback, Promise, Generator, and Async/Await. A Promise is not A new transaction, but A class implemented according to A specification. There are many such specifications, such as Promise/A, Promise/B, Promise/D and Promise/A’s upgrade, Promise/A+, and eventually the Promise/A+ specification was adopted in ES6. Later, Generator functions and Async functions are further encapsulated based on Promise, which shows the importance of Promise in asynchronous programming.
There was a lot of information about Promise, but everyone understood it differently, and different ideas yielded different results. This article will focus on the realization of Promise and some of my experiences in daily use.
Realize the Promise
Normative interpretation
Promise/A+ specification is mainly divided into terms, requirements and matters needing attention in three parts, we focus on the second part is the part of the requirements, to the author’s understanding roughly explain, specific details refer to the full version of Promise/A+ standard.
1, this is very depressing. (For consistency, this article calls the fulfilled state as the resolved state)
- The state transition can only be
pending
toresolved
orpending
torejected
;- Once the state has been converted, it cannot be converted again.
Promise has a THEN method that handles values in the Resolved or Rejected states.
then
Method takes two parametersonFulfilled
andonRejected
The two argument variables are of function type, otherwise they are ignored, and both arguments are optional.then
Method must return a new onepromise
, aspromise2
“That’s guaranteedthen
The method can be the samepromise
The previous call. (ps: The specification only requires returnpromise
Is not explicitly required to return a new onepromise
To be consistent with the ES6 implementation, we also return a newpromise
)onResolved/onRejected
If there is a return value, the return value is defined asx
[[Resolve]](promise2, x);onResolved/onRejected
Run error, thepromise2
Set torejected
State;onResolved/onRejected
If it’s not a function, you need to putpromise1
Is passed on.3. Different promise implementations can interact.
- The specification calls this step
promise
[[Resolve]](promise, x),promise
For the new to be returnedpromise
Object,x
foronResolved/onRejected
The return value of. ifx
There arethen
Method and it looks like apromise
Let’s just think of x as apromise
Object of, i.ethenable
Object, in which case try to makepromise
receivex
In the state. ifx
notthenable
Objectx
Value ofpromise
.- [[Resolve]](promise, x)
- if
promise
和x
Points to the same object asTypeError
Refuse to execute on grounds of evidencepromise
;- if
x
为Promise
, then makepromise
acceptx
The state;- if
x
For object or function, takex.then
If there is an error in the value, letpromise
Enter therejected
State, ifthen
It’s not a functionx
notthenable
Object, directly tox
The value of theresolve
If thethen
Exists and is a function, then thex
As athen
The scope of a functionthis
The call,then
Method takes two parameters,resolvePromise
andrejectPromise
If theresolvePromise
Is executed, thenresolvePromise
The parameters of thevalue
As ax
Continue calling [[Resolve]](promise, value) untilx
Not an object or a function, ifrejectPromise
Is executed to letpromise
Enter therejected
State;- if
x
Not an object or a function, just use itx
Value ofpromise
.
Code implementation
Article 1 of specification interpretation, code implementation:
class Promise {
// Define a Promise state with an initial value of pending
status = 'pending';
Since the then method needs to handle the Promise's success or failure value, a global variable is needed to store this value
data = ' ';
// The Promise constructor is passed as an executable function
constructor(executor) {
// The resolve function is responsible for changing the state to resolved
function resolve(value) {
this.status = 'resolved';
this.data = value;
}
// The reject function is responsible for converting the state to rejected
function reject(reason) {
this.status = 'rejected';
this.data = reason;
}
// Execute the executor function directly, taking the resolve, reject functions. Because the executor process can make errors, error cases need to be rejected
try {
executor(resolve, reject);
} catch(e) {
reject(e)
}
}
}
Copy the code
The first one is that the implementation is complete. It is relatively simple and easy to understand with code comments.
Specification interpretation article 2, code implementation:
/** * return a new Promise */; /** * return a new Promise */; /** * return a new Promise */
then(onResolved, onRejected) {
// Set the default parameters for then, which pass through the Promise values
onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return e };
onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e };
let promise2;
promise2 = new Promise((resolve, reject) = > {
// If the state is Resolved, execute onResolved
if (this.status === 'resolved') {
try {
// onResolved/onRejected
const x = onResolved(this.data);
// Execute [[Resolve]](promise2, x)
resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }}// If the state is rejected, then onRejected is executed
if (this.status === 'rejected') {
try {
const x = onRejected(this.data);
resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }}});return promise2;
}
Copy the code
Now that we have implemented article 2 of the specification, the code above is clearly problematic, and the problem is as follows
resolvePromise
Undefined;then
When the method executes,promise
May still be inpending
State, becauseexecutor
There may be asynchronous operations (most of which are actually asynchronous) in theonResolved/onRejected
Lost execution time;onResolved/onRejected
These two functions need to be called asynchronously (officialPromise
Implemented callback functions are always invoked asynchronously).
The solution:
- Read article 3 according to the specification, define and implement it
resolvePromise
Functions; then
Method is executed ifpromise
Is still in thepending
State, the processing function is stored, etcresolve/reject
When the function is actually executing.promise.then
It’s a microtask, so here we use macro tasks for conveniencesetTiemout
Instead of implementing asynchrony, the details are recommendedThis article.
Ok, with the solution in hand, let’s take the code one step further:
class Promise {
// Define a Promise state variable with the initial value pending
status = 'pending';
// Since we need to handle the Promise's success or failure value in the THEN method, we need a global variable to store this value
data = ' ';
// The set of callbacks for Promise resolve
onResolvedCallback = [];
// Set of callback functions for Promise reject
onRejectedCallback = [];
// The Promise constructor is passed as an executable function
constructor(executor) {
// The resolve function is responsible for changing the state to resolved
function resolve(value) {
this.status = 'resolved';
this.data = value;
for (const func of this.onResolvedCallback) {
func(this.data); }}// The reject function is responsible for converting the state to rejected
function reject(reason) {
this.status = 'rejected';
this.data = reason;
for (const func of this.onRejectedCallback) {
func(this.data); }}// Execute the executor function directly, taking the resolve, reject functions. Because the executor process can make errors, error cases need to be rejected
try {
executor(resolve, reject);
} catch(e) {
reject(e)
}
}
/** * return a new Promise */; /** * return a new Promise */; /** * return a new Promise */
then(onResolved, onRejected) {
// Set the default parameters for then, which pass through the Promise values
onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return v };
onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e };
let promise2;
promise2 = new Promise((resolve, reject) = > {
// If the state is Resolved, execute onResolved
if (this.status === 'resolved') {
setTimeout(() = > {
try {
// onResolved/onRejected
const x = onResolved(this.data);
// Execute [[Resolve]](promise2, x)
this.resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }},0);
}
// If the state is rejected, then onRejected is executed
if (this.status === 'rejected') {
setTimeout(() = > {
try {
const x = onRejected(this.data);
this.resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }},0);
}
// If the state is pending, the handler is stored
if (this.status = 'pending') {
this.onResolvedCallback.push(() = > {
setTimeout(() = > {
try {
const x = onResolved(this.data);
this.resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }},0);
});
this.onRejectedCallback.push(() = > {
setTimeout(() = > {
try {
const x = onRejected(this.data);
this.resolvePromise(promise2, x, resolve, reject);
} catch(e) { reject(e); }},0); }); }});return promise2;
}
// [[Resolve]](promise2, x
resolvePromise(promise2, x, resolve, reject){}}Copy the code
At this point, the then part of the specification is fully implemented. Detailed comments are added to the code, which are not hard to understand.
Article 3 of specification interpretation, code implementation:
// [[Resolve]](promise2, x
resolvePromise(promise2, x, resolve, reject) {
let called = false;
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise! '))}// If x is still a Promise
if (x instanceof Promise) {
// If the state of x has not been determined, then it is possible that the final state and value of x will be determined by a thenable, so continue calling resolvePromise
if (x.status === 'pending') {
x.then(function(value) {
resolvePromise(promise2, value, resolve, reject)
}, reject)
} else {
// If the x state is already determined, take it directly
x.then(resolve, reject)
}
return
}
if(x ! = =null && (Object.prototype.toString(x) === '[object Object]' || Object.prototype.toString(x) === '[object Function]')) {
try {
// Because x. hen may be a getter, in which case multiple reads may cause side effects, so the variable called is used to control
const then = x.then
// if then is a function, then x is thenable, then continue to execute the resolvePromise function until x is normal
if (typeof then === 'function') {
then.call(x, (y) = > {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
}, (r) = > {
if (called) return;
called = true; reject(r); })}else { Resolve x resolve x resolve x resolve x resolve x
if (called) return ;
called = true; resolve(x); }}catch (e) {
if (called) return;
called = true; reject(e); }}else{ resolve(x); }}Copy the code
This step is as simple as translating it into code according to the specification.
Resolve, promise. reject, and promise. all are not specified in the specification. Let’s take a look at the common methods of promises.
Promise other method implementations
1. Catch method
The CATCH method is an encapsulation of the THEN method and is only used to receive error messages in reject(Reason). If you do not pass the onRejected parameter then, the error message will be passed until you have onRejected. If you do not pass the onRejected parameter then, the error message will be passed until you have onRejected. All you need to do is add a catch() at the end of the chain, so that any errors that happen to the promise in the chain are caught by the last catch.
catch(onRejected) {
return this.then(null, onRejected);
}
Copy the code
2, done method
A catch is called at the end of a promise call and is used to catch errors in the chain. However, errors can also occur inside a catch method, so some promise implementations add a done method that provides an error-free catch method. It does not return a promise, usually to end a promise chain.
done() {
this.catch(reason= > {
console.log('done', reason);
throw reason;
});
}
Copy the code
3. Finally method
The finally method is used with either resolve or reject, and finally arguments are executed.
finally(fn) {
return this.then(value= > {
fn();
return value;
}, reason= > {
fn();
throw reason;
});
};
Copy the code
4, promise.all method
The promise. all method receives a Promise array, returns a new promise2, and executes all promises in the array. If all promises are in the Resolved state, promise2 is in the resolved state and returns all Promise results. The result is in the same order as the PROMISE array. If one promise is in the Rejected state, the whole Promise2 enters the rejected state.
static all(promiseList) {
return new Promise((resolve, reject) = > {
const result = [];
let i = 0;
for (const p of promiseList) {
p.then(value= > {
result[i] = value;
if(result.length === promiseList.length) { resolve(result); } }, reject); i++; }}); }Copy the code
5, promise.race method
The promise. race method receives a Promise array, returns a new promise2, and executes the promises in the array in order. If a Promise state is determined, the promise2 state is determined, and is consistent with the state of the Promise.
static race(promiseList) {
return new Promise((resolve, reject) = > {
for (const p of promiseList) {
p.then((value) = >{ resolve(value); }, reject); }}); }Copy the code
6, promise.resolve method/promise.reject
Promise.resolve generates a Promise (rejected), and promise. reject generates a Promise (rejected).
static resolve(value) {
let promise;
promise = new Promise((resolve, reject) = > {
this.resolvePromise(promise, value, resolve, reject);
});
return promise;
}
static reject(reason) {
return new Promise((resolve, reject) = > {
reject(reason);
});
}
Copy the code
There are many extension methods, which I won’t show you here. They are basically a further encapsulation of the THEN method. As long as your THEN method is fine, all other methods can rely on the THEN method.
Promise Interview
Interview related questions, the author only said the situation of our company in recent years, and can not represent the whole situation, reference can be. Promise is a required knowledge point for front-end development positions, NodeJS development positions and full stack development positions of our company. The main questions will be distributed in three aspects: Promise introduction, basic application methods and deep understanding. Generally, there are 3-5 questions, which will be increased or decreased appropriately according to the answers of the interviewees.
1. Brief introduction to Promise.
Promise is a solution to asynchronous programming that is more rational and powerful than the traditional solution of callbacks and events. It was first proposed and implemented by the community, and ES6 wrote it into the language standard, unifying usage and providing Promise objects natively. With Promise objects, you can express asynchronous operations as synchronized operations, avoiding layers of nested callbacks. In addition, Promise objects provide a uniform interface that makes it easier to control asynchronous operations. (Of course, you can also briefly introduce the promise state, what methods there are, what problems there are with the callback, etc., which is more open)
- Question probability: 99%
- Scoring standard: humanized judgment, this problem is generally as an introduction problem.
- Bonus points: Be able to say exactly what problems the Promise solves, what disadvantages it has, where it will be applied, etc.
2. Implement a simple Promise class that supports asynchronous chained calls.
The answer is not fixed. See minimalist Implementation Promise for support for asynchronous chained calls
- Probability of asking questions: 50% (hand-held code questions, because these questions are time-consuming, an interview does not come up with many, so they are not very frequent, but they are necessary)
- Bonus points: basic function implementation on the basis of
onResolved/onRejected
Function asynchronous call, reasonable error capture and other highlights.
3, the order in which promise. then is executed in the Event Loop. (You can either ask directly or give specific questions for the interviewer to answer in print order)
JS is divided into two types of tasks: macroTask and microtask, macroTask includes: main code block, setTimeout, setInterval, setImmediate, etc. (setImmediate stipulates: Triggered on the next Event Loop (macro task); Microtask include: Promise, process. NextTick, etc. If a microTask is encountered during execution, it is added to the task queue of the microtask. After the macroTask completes, it immediately executes all microTask tasks in the current microtask queue (in turn), and then starts the next MacroTask task (obtained from the event queue)
- Probability: 75% (3 out of 4 interviews
JS
Understanding the operating mechanism) - Bonus: The extension explains how the browser works.
4. Describe some static methods of Promise.
All, promise. race, promise. resolve, promise. reject, etc
- Probability of asking a question: 25% (basic question, usually asked when the answer to other questions is not satisfactory, or to lead to the next question)
- Bonus: The more the better
5. What are the disadvantages of Promise?
1. You cannot cancel a Promise. Once a Promise is created, it is executed immediately. If the callback function is not set, the Promise will throw an error that is not reflected externally. If you add a catch at the end of the Promise chain, there may still be an error that you can’t catch (and there may be an error inside of the catch). 4. Reading the code is not easy.
- Question probability: 25% (this question is used as an enhancement question, the probability of occurrence is not high)
- Bonus: The more, the more reasonable, the better.
(This topic, welcome to add the answer)
6. Use Promise for sequence processing.
Use async functions with await or generator functions with yield. 2. Use promise.then through the for loop or array.prototype.reduce.
function sequenceTasks(tasks) {
function recordValue(results, value) {
results.push(value);
return results;
}
var pushValue = recordValue.bind(null[]);return tasks.reduce(function (promise, task) {
return promise.then(() = > task).then(pushValue);
}, Promise.resolve());
}
Copy the code
- The question probability: 90% (our company asks the question with high probability, that is, can check the interviewer’s right
promise
You can look at the logic of the program, and finallybind
andreduce
Etc.) - Rating criteria: Name any solution, but only name one
async
Functions andgenerator
You can get 20% of the score of the function, you can usepromise.then
Cooperate withfor
You can get 60% of the score if you do it in a circular wayArray.prototype.reduce
Those who do get the final 20%.
How to stop a Promise chain?
Add a method at the end of the promise chain that returns a promise that never executes resolve or Reject. The promise is always pending, so it will never execute then or catch down. So we’ve stopped a promise chain.
Promise.cancel = Promise.stop = function() {
return new Promise(function(){})}Copy the code
- Probability: 50% (this question mainly examines the interviewer’s thinking)
(This topic, welcome to add the answer)
8. What if the last Promise returned on the Promise chain is wrong?
A catch is called at the end of a promise call and is used to catch errors in the chain. However, errors can also occur inside a catch method, so some promise implementations add a done method that provides an error-free catch method. It does not return a promise, usually to end a promise chain.
done() {
this.catch(reason= > {
console.log('done', reason);
throw reason;
});
}
Copy the code
- Question probability: 90% (also as a very high rate of a question, fully investigate the interviewer’s right
promise
Understanding of) - Bonus: Be specific
done()
Method code implementation
9. What are Promise use techniques or best practices?
1. Chained promise returns a promise, not just constructs one. 2, reasonable use promise. all and promise. race and other methods. You can only add a catch() to the end of the promise chain, so all the errors in the promise chain will be caught by the catch(). If the catch() code has the potential for an error, the done() function needs to be added at the end of the chained call.
- Question probability: 10% (a question with very low question probability)
- Bonus: The more the better
(This topic, welcome to add the answer)
So far, we have listed some interview questions about Promise, some questions are open, welcome to complement and improve. To sum up, Promise is relatively easy to master and pass as part of the js interview requirement.
conclusion
Promise as a necessary skill for all JS developers, its implementation idea is worth learning by all people, through this article, I hope that friends in the future coding process can more skilled, more understand the use of Promise.
Reference links:
Liubin.org/promises-bo… Github.com/xieranmaya/… Segmentfault.com/a/119000001…