This article was first published on vivo Internet Technology WeChat Public Number
Links: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ
Author: Kong Chuan Liang
When learning Promise, many students know it but don't know why. They don't understand its usage.This series of articles gradually implements Promise from shallow to deep, and demonstrates it with flowcharts, examples, and animations to gain a deep understanding of Promise usage.
This series of articles consists of the following chapters:
-
Graphical Promise Implementation Principle (1) - Basic Implementation
-
Diagram of Promise Implementation Principle (2) - Promise Chain Call
-
Diagram of Promise Implementation Principle (3) - Implementation of Promise Prototype Method
-
Graphical Promise Implementation Principle (4) - Promise Static Method Implementation
This article is suitable for people who know the use of Promise. If you are not sure, please refer to Mr. Ruan Yifeng's " Promise Object for ES6 Getting Started>.
There are many Promise specifications, such as Promise/A, Promise/B, Promise/D, and the upgraded version of Promise/A+ of Promise/A. It is interesting to know that the Promise/A+ specification was adopted in ES6.So the Promise source for this article is as follows Promise/A+ Specification To write (do not want to see the English version of the move) Promise/A+ Specification for Chinese Translation).
Introduction
In order to make it easier for everyone to understand, we start from a scene, think step by step, and it will be easier to understand.
Consider one of the following request processing for user id:
//Do not use Promise http.get('some_url', function (result) { //do something console.log(result.id); }); //Use Promise new Promise(function (resolve) { //Asynchronous request http.get('some_url', function (result) { resolve(result.id) }) }).then(function (id) { //do something console.log(id); })
At first glance, it seems simpler not to use Promise.Otherwise, imagine if there are several dependent pre-requests that are asynchronous, and if there is no Promise at this time, the callback functions need to be nested layer by layer, which looks uncomfortable.The following:
//Do not use Promise http.get('some_url', function (id) { //do something http.get('getNameById', id, function (name) { //do something http.get('getCourseByName', name, function (course) { //dong something http.get('getCourseDetailByCourse', function (courseDetail) { //do something }) }) }) }); //Use Promise function getUserId(url) { return new Promise(function (resolve) { //Asynchronous request http.get(url, function (id) { resolve(id) }) }) } getUserId('some_url').then(function (id) { //do something return getNameById(id); // getNameById is the same Promise encapsulation as getUserId.Same as below }).then(function (name) { //do something return getCourseByName(name); }).then(function (course) { //do something return getCourseDetailByCourse(course); }).then(function (courseDetail) { //do something });
Implementation Principle
In the end, Promise also uses callback functions, just encapsulating callbacks inside, using chained callbacks that have been called through the then method, making multilevel nesting of callbacks look the same, more intuitive and concise in writing and understanding.
1. Basic version
//Realization of minimalism class Promise { callbacks = []; constructor(fn) { fn(this._resolve.bind(this)); } then(onFulfilled) { this.callbacks.push(onFulfilled); } _resolve(value) { this.callbacks.forEach(fn => fn(value)); } } //Promise Application let p = new Promise(resolve => { setTimeout(() => { console.log('done'); resolve('5 second'); }, 5000); }).then((tip) => { console.log(tip); })
The above code is simple, and the general logic is as follows:
-
Calling the then method places onFulfilled, which you want to execute when the Promise asynchronous operation succeeds, in the callbacks queue, which is actually a registered callback function that you can think about in the direction of observer mode.
-
The function passed in when the Promise instance is created is assigned a parameter of type function, Resolution, which receives a parameter value representing the result returned by the asynchronous operation. When the asynchronous operation succeeds, the resolve method is called, and what it actually does is to execute the callbacks in the callbacks queue one by one.
(Fig. Basic version implementation principle)
The function settings timer passed to Promise first simulates an asynchronous scenario, then calls the then method of the Promise object to register onFulfilled after the asynchronous operation completes, and finally, when the asynchronous operation completes, calls resolve(value) to execute the onFulfilled registered by the then method.
The onFulfilled registered by the then method is an array in which the then method can be called multiple times, and the registered onFulfilled is executed in the order in which it was added after the asynchronous operation is completed.The following:
//Description of the let p = new Promise(resolve => { setTimeout(() => { console.log('done'); resolve('5 second'); }, 5000); }); p.then(tip => { console.log('then1', tip); }); p.then(tip => { console.log('then2', tip); });
In the example above, you define a variable p, then p.then twice.The specification requires that the then method should be able to be called chained.The implementation is also simple, just return this in the n.As follows:
//Minimalist implementation + Chain call class Promise { callbacks = []; constructor(fn) { fn(this._resolve.bind(this)); } then(onFulfilled) { this.callbacks.push(onFulfilled); return this;//Look here } _resolve(value) { this.callbacks.forEach(fn => fn(value)); } } let p = new Promise(resolve => { setTimeout(() => { console.log('done'); resolve('5 second'); }, 5000); }).then(tip => { console.log('then1', tip); }).then(tip => { console.log('then2', tip); });
(Figure: Chain call for the base version)
2. Join the Delay Mechanism
One problem with the implementation of Promise above is that if resolve executes before the then method registers onFulfilled, onFulfilled will not execute.For example, in the example above, we remove setTimout:
//resolve executed synchronously let p = new Promise(resolve => { console.log('Synchronous execution'); resolve('Synchronous execution'); }).then(tip => { console.log('then1', tip); }).then(tip => { console.log('then2', tip); });
Execution results show that only "Synchronized Execution" is printed, and the following "then1" and "then2" are not printed.Looking back at Promise's source code, it's also easy to understand that when resolve executes, callbacks are empty arrays and have not yet been registered with onFulfilled.
This is obviously not allowed, and the Promises/A+ specification explicitly requires callbacks to be executed asynchronously to ensure a consistent and reliable execution sequence.So add some processing to ensure that the then method has registered all callbacks before resolve executes:
//Minimalist implementation+Chain call+Delay mechanism class Promise { callbacks = []; constructor(fn) { fn(this._resolve.bind(this)); } then(onFulfilled) { this.callbacks.push(onFulfilled); return this; } _resolve(value) { setTimeout(() => {//Look here this.callbacks.forEach(fn => fn(value)); }); } }
Add a timer to the resolve and place the logic of the callback execution in the resolve at the end of the JS task queue through the setTimeout mechanism to ensure that the onFulfilled of the then method is registered when the resolve executes.
(Fig. Delay mechanism)
However, there are still problems. After resolve has been executed, onFulfilled registered via the then has no opportunity to execute.As shown below, then1 and then2 can be printed with a delay, but then3 in the example below still cannot be printed.So we need to increase the state and save the resolve value.
let p = new Promise(resolve => { console.log('Synchronous execution'); resolve('Synchronous execution'); }).then(tip => { console.log('then1', tip); }).then(tip => { console.log('then2', tip); }); setTimeout(() => { p.then(tip => { console.log('then3', tip); }) });
3. Increased status
To solve the problems thrown out in the previous section, we must incorporate a state mechanism, known as pending, fulfilled, rejected.
The Promises/A+ specification explicitly states that pending can be converted to fulfilled or rejected and can only be converted once, that is, if pending is converted to fulfilled, it cannot be converted to rejected.And fulfilled and rejected states can only be converted from pending, and they cannot be converted to each other.
This is what happens when the state is increased
//Minimalist implementation+Chain call+Delay mechanism+State class Promise { callbacks = []; state = 'pending';//Increase status value = null;//Save results constructor(fn) { fn(this._resolve.bind(this)); } then(onFulfilled) { if (this.state === 'pending') {//Before resolve, add to callbacks as before this.callbacks.push(onFulfilled); } else {//After resolve, the callback is executed directly and the result is returned onFulfilled(this.value); } return this; } _resolve(value) { this.state = 'fulfilled';//Change state this.value = value;//Save results this.callbacks.forEach(fn => fn(value)); } }
Note: When the state is added, the timer in the original_resolve can be removed.When reolve synchronizes execution, although callbacks are empty and callback functions have not been registered, that's okay, because when later registered, the callback will be immediately executed if the status is fulfilled.
(Figure: Promise Status Management)
Only fulfilled States and onFulfilled callbacks are added to the implementation source, but rejected and onRejected are added to the diagram for integrity.Later chapters will be implemented.
When resolve executes, it sets the state to fulfilled, saves the value of the value, and after that calls the new callback added by the n, it executes immediately, returning the saved value directly.
(Promise state change demo animation)
For more information, click: https://mp.weixin.qq.com/s/UNzYgpnKzmW6bAapYxnXRQ
So far, a functional Promise has been implemented, which implements then, chained calls, state management, and so on.But consider carefully that the implementation of a chain call simply returns this in the then, because the same instance can only return the same result if the call is made multiple times, which obviously does not meet our requirements.The next section describes how to implement a true chain call.
More please pay attention to vivo Internet technology WeChat Public Number
Note: For the article to be reproduced, please contact Microsignal: labs2020 first.