In this paper, starting from vivo Internet technology WeChat public links: mp.weixin.qq.com/s/u8wuBwLpc… Author: Morrain
Promise is a solution of asynchronous programming. It was first proposed and implemented by the community. ES6 has written it into the language standard, unified the usage, and provided the Promise object natively. For more information about promises, please refer to Ruan Yifeng’s ES6 Introduction to Promise Objects.
Many students know the Promise but don’t know its reason and can’t understand its usage. This series of articles gradually realize Promise from simple to deep, and combined with flow charts, examples and animations to demonstrate, to achieve a deep understanding of the purpose of Promise usage.
This article series consists of the following chapters:
-
Promise Implementation Principle (I) — Basic implementation
-
(2) – The Promise chain call
-
Graphic Promise implementation principle (three) – Promise prototype method implementation
-
Schematic Promise implementation principle (four) – Promise static method implementation
I. [Preface]
In the previous section, you implemented the chain call of Promise. The chain call is the difficulty, and more important, of Promise. So far, the Promise implementation is as follows:
class Promise {
callbacks = [];
state = 'pending'; // Add status value = null; Constructor (fn) {fn(this._resolve.bind(this)); }then(onFulfilled) {
return new Promise(resolve => {
this._handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
}
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return; } / / ifthenNothing is passed inif(! callback.onFulfilled) { callback.resolve(this.value);return;
}
var ret = callback.onFulfilled(this.value);
callback.resolve(ret);
}
_resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then= = ='function') {
then.call(value, this._resolve.bind(this));
return;
}
}
this.state = 'fulfilled'; This. value = value; // save the result this.callbacks. ForEach (callback => this._handle(callback)); }}Copy the code
This section mainly introduces the implementation of the Promise prototype method, including the implementation of catch, finally and Rejected state.
Ii. [Error Handling]
This is a big pity. Before, in order to explain the principle, there is only onFulfilled. For Promise, in addition to success, there is also failure. As shown in the following Demo:
/**
* 模拟异常异步请求
* @param {*} url
* @param {*} s
* @param {*} callback
*/
const mockAjax = (url, s, callback) => {
setTimeout(() => {
callback(url + 'Asynchronous request Time' + s + '秒'.'Wrong! ');
}, 1000 * s)
}
//demo reject
new Promise((resolve, reject) => {
mockAjax('getUserId', 1, function (result, error) {
if (error) {
reject(error)
} else {
resolve(result);
}
})
}).then(result => {
console.log(result);
}, error => {
console.log(error);
});Copy the code
With our previous experience with depressing states, it’s easy to support error handling by adding new Reject logic to register callbacks and handle state changes.
// complete implementation +reject class Promise {callbacks = []; state ='pending'; // Add status value = null; Constructor (fn) {fn(this._resolve.bind(this), this._reject. Bind (this)); }then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
this._handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
if(! If cb) {/ /thenCb = this.state ==='fulfilled' ? callback.resolve : callback.reject;
cb(this.value);
return;
}
let ret = cb(this.value);
cb = this.state === 'fulfilled' ? callback.resolve : callback.reject;
cb(ret);
}
_resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then= = ='function') {
then.call(value, this._resolve.bind(this), this._reject.bind(this));
return;
}
}
this.state = 'fulfilled'; This. value = value; // save the result this.callbacks. ForEach (callback => this._handle(callback)); } _reject(error) { this.state ='rejected'; this.value = error; this.callbacks.forEach(callback => this._handle(callback)); }}Copy the code
The source code of the demo – reject
The running results are as follows:
[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function],
onRejected: [Function],
resolve: [Function],
reject: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null} [promse-1]:_reject [promse-1]:_reject value= Error! [promse-1]:_handle state= rejected [Promse-2]:_reject [Promse-2]:_reject value= undefinedCopy the code
Iii. [Exception Handling]
Error handling, which you just introduced, is an error found in the Promise constructor and notified through Reject. What should I do if there is an exception during the implementation of onFulfilled or onRejected? You can catch the error with a try-catch and then set the corresponding Promise state to the Rejected state. The method of modifying _HANDLE is as follows:
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
let cb = this.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
if(! If cb) {/ /thenCb = this.state ==='fulfilled' ? callback.resolve : callback.reject;
cb(this.value);
return;
}
let ret;
try {
ret = cb(this.value);
cb = this.state === 'fulfilled'? callback.resolve : callback.reject; } catch (error) { ret = error; cb = callback.reject } finally { cb(ret); }}Copy the code
Demo – error source
Reject = reject (reject); reject (reject); Reject (reject); reject (reject); So add a separate catch method, which is an alias to.then(null, onRejected). As follows:
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
this._handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
}
catch(onError){
return this.then(null, onError);
}Copy the code
The source code of the demo – catch
4. 【Finally 】
In practice, it’s easy to run into situations where some action (onDone) has to be performed regardless of the final state of the Promise. For example, the server uses Promise to handle the request, and then uses the finally method to shut down the server:
server.listen(port)
.then(function () {
// do something
})
.catch(error=>{
// handle error
})
.finally(server.stop);Copy the code
Essentially, because it’s a variation of then. The effect of the above demo is equivalent to the following code:
server.listen(port)
.then(function () {
// do something
})
.catch(error=>{
// handle error
})
.then(server.stop, server.stop);Copy the code
From the above analysis, finally looks like this:
finally(onDone){
return this.then(onDone, onDone);
}Copy the code
However, since onDone of the finally method does not care about whether the Promise state is fulfilled or rejected, the operation in onDone should be independent of the state and should not have any parameters.
Using THEN does not comply with the Promise specification for Why not. Then (f, f)? The instructions. OnDone has arguments, and when onDone returns a Promise, it changes the state of the Promise value finally returns.
According to the specification, finally is implemented as follows:
catch(onError) {
return this.then(null, onError);
}
finally(onDone) {
if(typeof onDone ! = ='function') return this.then();
let Promise = this.constructor;
return this.then(
value => Promise.resolve(onDone()).then(() => value),
reason => Promise.resolve(onDone()).then(() => { throw reason })
);
}Copy the code
The source code of the demo – finally
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000)
}).finally(() => {
console.log('onDone')})Copy the code
For the example above, the result is as follows:
[Promse-1]:constructor
[Promse-1]:finally
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function],
onRejected: [Function],
resolve: [Function],
reject: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= success
[Promse-1]:_handle state= fulfilled
onDone
Promise::resolve
[Promse-3]:constructor
[Promse-3]:_resolve
[Promse-3]:_resolve value= undefined
[Promse-3]:then
[Promse-4]:constructor
[Promse-3]:_handle state= fulfilled
[Promse-4]:_resolve
[Promse-4]:_resolve value= success
[Promse-2]:_resolve
[Promse-2]:_resolve value= Promise {
callbacks: [],
name: 'Promse-4',
state: 'fulfilled',
value: 'success' }
[Promse-4]:then
[Promse-5]:constructor
[Promse-4]:_handle state= fulfilled
[Promse-2]:_resolve
[Promse-2]:_resolve value= success
[Promse-5]:_resolve
[Promse-5]:_resolve value= undefinedCopy the code
You can also recreate the process by gesturing an animation:
(Promise.finally demo animation) Click open >>
The finally implementation looks simple, but it’s actually difficult to understand. In the example above, there were actually five Promise instances generated. As shown below:
So much for the prototype method, the next section introduces two static methods
For more content, please pay attention to vivo Internet technology wechat public account
Note: To reprint the article, please contact our wechat account: Labs2020.