1. Introduction
There was a previous article about eventLoop in detail. This article will go deep into promise, study the implementation of promise in detail, and fundamentally solve some misunderstanding of promise. If the interviewer has a very deep understanding of promise, he will not only ask you what promse.all does. He will combine setTimeout and promise to confuse you and see if you have mastered it fundamentally.
I often ask those same questions I promise when INTERVIEWING people. So today, follow my questions and uncover the underlying implementation of Promise. In order to understand this article, you need to have a relatively clear understanding of event loops. I suggest you take a look at my article: # From setTimeout to Event-loop
2. Start with a short question that you can answer in person
setTimeout(() => {console.log(1)}, 0);
new Promise((resolve, reject) => {
console.log(2);
resolve('res1');
console.log(3);
}).then(res => console.log(4))
.then(data => console.log(5))
setTimeout(() => {
console.log(6);
Promise.resolve().then(res => console.log(7));
}, 0);
setTimeout(() => {
console.log(8)
})
console.log(9);
Copy the code
You can read the results silently, and then run to see the results. If you’re right, congratulations, you’ve mastered the basics of promise pretty well.
So let’s move on to the real scenario where promises are used.
3. Examples of real use scenarios of Promise
Let’s do another one:
To implement a small feature, make the first method complete -> sleep3 seconds -> the second method complete
To implement a sleep function, the logic needs to have an execution order. And you can try to do that.
Currently we can implement async await or promise. Since this article introduces promises, use promises to implement them.
// Create a queue
const queue = [];
const fn1 = () = > new Promise((resolve, reject) = > {
console.log('fn1 executed');
resolve();
});
const sleep = () = > new Promise((resolve, reject) = > {
setTimeout(() = > {
console.log('sleep executed');
resolve();
}, 3000)});const fn2 = () = > new Promise((resolve, reject) = > {
console.log('fn2 executed');
resolve();
});
queue.push(fn1, sleep, fn2);
const execute = () = > {
/ / execution
let queueExe = Promise.resolve();
for(let i=0; i< queue.length; i++) { queueExe = queueExe.then(queue[i]); }}/ / execution
execute();
Copy the code
Promise is so powerful that it can handle more than just one asynchronous request. Through the above two questions, one of the more confusing is:
- It’s said that making promises is a microtask, but why in Example 1
Console. log in new Promise() will execute immediately
? Where is the microtask embodied? - How can promises be executed sequentially through methods like Resolve?
So it’s necessary to fulfill a promise yourself.
4. The code implements a Promise
In view of the above mentioned two problems, to master from the bottom, the best way to implement again. Therefore, I wrote a promise to help people understand and deepen their memories. After all, if I don’t get familiar with it for a long time, I will forget some details.
4.1 Core Ideas
Start by framing your mind
const MyPromise = (fn) = > {
let status = 'pending';
let newValue = null;
this.then = (onFulfilled) = > {
// Because then can be called chained, and we know that then returns a new promise
return new MyPromise((resolve, reject) = > {
// ...})}const resolve = (value) = >{}const reject = (value) = >{}// Pass resolve and reject
fn(resolve, reject);
}
new MyPromise((resolve, reject) = > {
// ...
resolve('step1 done');
}).then((res) = > {
// ...
}).then(res= > {
// ...
})
Copy the code
So far it’s just a simple framework, but it’s the core. Here we will focus on how ‘resolve’ and ‘reject’ are created. We will also focus on how ‘pending’ and ‘depressing’ are implemented inside then(), rather than just calls.
4.2 Filling the Frame
Let’s start filling in the opposite logic. The reject logic is not implemented for the sake of clarity for the subject logic.
function MyPromise(cb) {
const callbacks = [];
let status = 'pending';
let newValue = null;
const handle = (fnObj) = > {
// Handle execution issues
if(status === 'pending') {
callbacks.push(fnObj);
return;
}
/ / execution
if(! fnObj.onFulfilled) { resolve(newValue);return;
}
const res = fnObj.onFulfilled(newValue);
fnObj.resolve(res);
}
this.then = (onFulfilled) = > {
// Note the context in which this callback is executed
return new MyPromise((resolve) = > {
handle({
onFulfilled,
resolve
})
})
}
const resolve = (value) = > {
const handleCb = () = > {
while(callbacks.length) {
letretCb = callbacks.shift(); handle(retCb); }}// Put it in the task queue for execution in the next event loop
const fn = () = > {
if(status ! ='pending') return;
status = 'fulfilled';
newValue = value;
// Go to callbacks for callback execution
handleCb();
}
// This simulation is not appropriate because it is set to macro tasks, which are actually micro tasks
// But specific microtasks are handled in V8, so
// For now, only setTimeout can be used
setTimeout(fn, 0);
}
cb(resolve);
}
new MyPromise((resolve) = > {
console.log(1);
resolve('init');
resolve('new data') // It will not be executed here
}).then((res) = > {
console.log(res);
}).then((data) = > {
console.log(3);
})
Copy the code
That way, you can make basic promises. Two difficulties:
- The role and implementation of Resolve. That is, when the time is right for the microtask (simulated using setTimeout in the example) to execute, the
callbacks
To obtain the CB. - The role of then. Then is called and instantiated
MyPromise
Function, and consider the callback in then asonFulfilled
Functions are stored in callbacks.
These two steps are somewhat convoluted, especially when the then method calls MyPromise.
4.3 More complex scenarios
If you understand the code above, let’s look at some more complex scenarios. We implement reject and the corresponding catch logic.
In fact, there is also a corresponding interview pit. When this chapter is over, I believe I have a deeper understanding of Reject.
function MyPromise(cb) {
const callbacks = [];
let status = 'pending';
let newValue = null;
const handle = (fnObj) = > {
// Handle execution issues
if(status === 'pending') {
callbacks.push(fnObj);
return;
}
const callback = status === 'fulfilled' ? fnObj.onFulfilled : fnObj.onRejected;
const execute = status === 'fulfilled' ? fnObj.resolve : fnObj.reject;
if(! callback) { execute(newValue);return;
}
try{
const result = callback(newValue);
// Assign the return value of the callback function in the page code to the corresponding resolve or reject function
// If no value is returned, undefined
execute(result);
} catch(e) {
// enter reject
fnObj.reject(e)
}
}
// In the Promise API, reject can also be captured in then via a second callback
this.then = (onFulfilled, onRejected) = > {
// Note the context in which this callback is executed
return new MyPromise((resolve, reject) = > {
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
// More often, the catch method is used
this.catch = (onReject) = > {
// Return a new promise
// We can call this.then directly
this.then(null, onReject);
}
const handleCb = () = > {
while(callbacks.length) {
letretCb = callbacks.shift(); handle(retCb); }}const resolve = (value) = > {
// Put it in the task queue for execution in the next event loop
const fn = () = > {
if(status ! ='pending') return;
status = 'fulfilled';
newValue = value;
// Go to callbacks for callback execution
handleCb();
}
// This simulation is not appropriate because it is set to macro tasks, which are actually micro tasks
// But specific microtasks are handled in V8, so
// For now, only setTimeout can be used
setTimeout(fn, 0);
}
const reject = (value) = > {
Resolve is the same as resolve in principle
const fn = () = > {
if(status ! = ='pending') return;
status = 'rejected';
newValue = value;
handleCb();
}
setTimeout(fn, 0);
}
cb(resolve, reject);
}
new MyPromise((resolve, reject) = > {
console.log(1);
reject('init error');
}).then((res) = > {
console.log(res);
}).then((data) = > {
console.log(3);
}).catch((e) = > {
console.log('Error caught', e);
})
// The result is:
/ / 1
// Init error was caught
Copy the code
4.4 More complex scenarios
By doing this, we’ve completed 80% of the Promise functionality. The rest are then or catch scenarios with asynchronous data fetching. In this scenario, we need to wait for the data to succeed before continuing with the following then or catch. Here’s another example:
new MyPromise((resolve, reject) = > {
console.log(1);
resolve('https://get/asncyData/api');
}).then((res) = > {
let resData = null;
axios.get(res).then(data= > {
// Get the data right
resData = data;
})
}).then((data) = > {
// What should we do to get the real output from the above?
console.log(data);
}).catch((e) = > {
console.log('Error caught', e);
})
Copy the code
The best way to get the result of the request in AXIos in the second THEN is either with a Promise or with async await. Since we study Promise here, we will study Promise thoroughly and realize it with Promise.
new MyPromise((resolve, reject) = > {
console.log(1);
resolve('https://get/asncyData/api');
}).then((res) = > {
return new MyPromise((resolve, reject) = > {
let resData = null;
axios.get(res).then(data= > {
// Get the data right
resData = data;
resolve(resData);
})
})
}).then((data) = > {
// Get the data from the previous then
console.log(data);
}).catch((e) = > {
console.log('Error caught', e);
})
Copy the code
In general, this is the more complex scenario we encounter: Promise within Promise.
When executing the callback method in THEN, you need to determine if the callback is an instance of MyPromise, and if so, wait until the callback completes before continuing. The logic involved in this is quite brain-burning, involving recursive processing of some things, I suggest you slowly read, do not turn over a half-knowledge.
Here’s the code:
let flag = 0; // Use it as an aid to view the relationship, or not
function MyPromise(cb) {
debugger;
this.callbacks = [];
let status = 'pending';
let newValue = null;
this.flag = flag++;
const handle = (fnObj) = > {
// Handle execution issues
if(status === 'pending') {
this.callbacks.push(fnObj);
return;
}
const callback = status === 'fulfilled' ? fnObj.onFulfilled : fnObj.onRejected;
const execute = status === 'fulfilled' ? fnObj.resolve : fnObj.reject;
if(! callback) { execute(newValue);return;
}
try{
const result = callback(newValue);
// Assign the return value of the callback function in the page code to the corresponding resolve or reject function
// If no value is returned, undefined
execute(result);
} catch(e) {
// enter reject
fnObj.reject(e)
}
}
// In the Promise API, reject can also be captured in then via a second callback
this.then = (onFulfilled, onRejected) = > {
// Note the context in which this callback is executed
return new MyPromise((resolve, reject) = > {
//
handle({
onFulfilled,
onRejected,
resolve,
reject
})
})
}
// More often, the catch method is used
this.catch = (onReject) = > {
// Return a new promise
// We can call this.then directly
this.then(null, onReject);
}
const handleCb = () = > {
// console.log('callbacks:::', this);
while(this.callbacks.length) {
let retCb = this.callbacks.shift(); handle(retCb); }}const resolve = (value) = > {
// Put it in the task queue for execution in the next event loop
const fn = () = > {
if(status ! ='pending') return;
// After executing the handle of the previous context, execute the resolve of the current context
if (typeof value === 'object' && value instanceof MyPromise) {
// The promise needs to be inserted into the queue to execute
const {then} = value;
then.call(value, resolve, reject);
return;
}
status = 'fulfilled';
newValue = value;
// Go to callbacks for callback execution
handleCb();
}
// This simulation is not appropriate because it is set to macro tasks, which are actually micro tasks
// But specific microtasks are handled in V8, so
// For now, only setTimeout can be used
setTimeout(fn, 0);
}
const reject = (value) = > {
Resolve is the same as resolve in principle
const fn = () = > {
if(status ! = ='pending') return;
status = 'rejected';
newValue = value;
handleCb();
}
setTimeout(fn, 0);
}
cb(resolve, reject);
}
//
new MyPromise((resolve, reject) = > {
console.log(1);
resolve('https://get/asncyData/api');
}).then((res) = > {
return new MyPromise((resolve, reject) = > {
setTimeout(() = > {
resolve({code: 0.msg: 'Simulate Ajax to get results returned from data'})},2000);
})
}).then((data) = > {
// Get the data from the previous then
console.log('Data acquired ::', data);
}).catch((e) = > {
console.log('Error caught', e);
})
Copy the code
The first steps are basically linear logic, which is relatively easy to understand, while the last complex scene requires careful understanding. It took me quite a while to figure out the last part myself.
Pure ES6 version
/** * Implement a MyPromise with class * On top of promise-base, implement more complex functionality: what to do when another promise needs to be processed in the then method */
const _status = new WeakMap(a);const _resolveValue = new WeakMap(a);class MyPromise {
callbacks = [];
constructor(cb) {
// Private variables can be defined using WeakMap
_status.set(this.'pending');
_resolveValue.set(this.void 0);
// Define local variables
const handle = (fnField) = > {
const status = _status.get(this);
if(status === 'pending') {
this.callbacks.push(fnField);
return;
}
// Otherwise execute
const callback = status === 'fulfilled' ? fnField.onFulfilled : fnField.onRejected;
const execute = status === 'fulfilled'? fnField.resolve : fnField.reject;
if(! callback) { execute(_resolveValue.get(this));
return;
}
try {
const result = callback(_resolveValue.get(this));
execute(result);
} catch(error) { fnField.reject(error); }}const handleCb = () = > {
while(this.callbacks.length) {
const targetCb = this.callbacks.shift(); handle(targetCb); }}const resolve = (value) = > {
const fn = () = > {
// The purpose is to fetch the corresponding callback and execute it
if(_status.get(this)! = ='pending') {
return;
}
// If the value returned is a new promise, then the new promise needs to be inserted into the callback in the next THEN
// before execution, otherwise there will be a problem with timing
if(typeof value === 'object' && value instanceof MyPromise) {
value.then(resolve, reject);
return;
}
_status.set(this.'fulfilled');
_resolveValue.set(this, value);
handleCb();
}
setTimeout(fn, 0);
}
const reject = (errorMsg) = > {
const fn = () = > {
if(_status.get(this)! = ='pending') {
return;
}
if(typeof value === 'object' && value instanceof MyPromise) {
value.then(resolve, reject);
return;
}
_status.set(this.'rejected');
_resolveValue.set(this, value);
handleCb();
}
setTimeout(fn, 0);
}
// Define instance methods and attributes
this.then = (onFulfilled, onRejected) = > {
return new MyPromise((resolve, reject) = >{ handle({ onFulfilled, onRejected, resolve, reject }) }) } cb(resolve, reject); }}new MyPromise((resolve, reject) = > {
console.log(1);
// Simulates asynchronous Ajax operations
setTimeout(() = > {
resolve('init promise executed');
}, 1000);
}).then((res) = > {
console.log(2, res);
return new MyPromise((resolve, reject) = > {
setTimeout(() = > {
// Resolve is the outer then method
resolve('The goal promise has been fulfilled.');
}, 2000);
})
}).then((res) = > {
console.log(3, res);
})
Copy the code
Afterword.
This article is not a complete promise implementation, missing some methods, and not as robust. But from the perspective of in-depth analysis, it should be enough.
Because work is busy, since August 30, beginning to September 7th formal written today, took a week’s time, a little slow, but is outside the work time to knock out a word a word, the code is from 0 to 1, the realization of the simple to the difficult bit by bit, so, you think, this is a sincerity, welcome to browse and communication ~ ~