When writing promise, I found myself using tree, linked list, forward traversal and other methods. I thought it would be helpful for some students who want to know the promise mechanism and deepen their learning of data structure, so I decided to write an article to share it. But writing is really worrying, no patience to see the students, you can directly look at the source code. Source code address.
When did the promise status change?
1: State change for a Promise generated by new Promise() : when resolve is called:
Promise1 = new Promise((resolve) => {setTimeout(() => {resolve(' resolve ')); }, 1000)})Copy the code
Case 1: When the argument is a non-PROMISE, the state of the Promise immediately changes to resolve after 1 second, and the events in then are executed.
Situation 2: promise1 = new Promise((resolve) => { setTimeout(() => { promise2 = new Promise((resolve, reject) => { resolve('promise2'); }) resolve(promise2); }, 1000)})Copy the code
Case 2: When the parameter is another promise, the state of promise1 is determined by promisE2. When promisE2 changes its state, the state of promisE1 changes accordingly and remains the same.
When calling reject:
Unlike resolve,reject immediately changes to reject regardless of the argument.
2: State change process of promise generated by then() or catch()
Promise1 = new Promise((resolve) => {resolve(' resolve '); }) promise2 = promise1.then((data) => { return 'promise2'; })Copy the code
Promise2 (resolve) : when a non-promise is returned, the state changes to resolve. Resolve (‘ non-promise ‘)
Promise1 = new Promise((resolve) => {resolve(' resolve '); }) promise2 = promise1.then((data) => { promise3 = new Promise((resolve, reject) => { resolve('promise3'); }) return promise3; })Copy the code
(resolve(promise3)) (resolve(promise3))
Promise1 = new Promise((resolve) => {resolve(' resolve '); }) promise2 = promise1.then((data) => { console.log( iamnotundefined ); })Copy the code
Case 3: When there is an error in the callback and no catch is made, the current promise state changes to reject.
Just a couple of promise examples
promise1 = new Promise((resolve, reject) => { setTimeout(()=>{ resolve('promise1_resolve_data'); },1000) }) console.log(promise1); promise2 = promise1.then((data) => { console.log('promise2---', data); return 'promise2'; }) console.log(promise2); promise3 = promise1.then((data) => { console.log('promise3---', data); return 'promise3'; }) console.log(promise3); setTimeout(() => { console.log('--promise1--', promise1); console.log('--promise2--', promise2); console.log('--promise3--', promise3); }, 3000).Copy the code
Code execution result:
In turn output promise1 promise2 promise3, state is pendding. After one second, execute relove, and the state changes to resolve with the value ‘promise1_resolve_data’. Then execute the callbacks in promise1. Then, and the state of promise2 changes to ‘promise2’. The state of promise3 changes to ‘promise3’.
What does the above snippet show?
1: when initializing promise1 promise2, promise3, three state of promise is pendding. 2: when the inside of the promise1 resolve after execution, promise1 state immediately to resolve the value to resolve the function parameters. 3: Promise2 and promise3 are generated by the THEN method of promise1, and the state of promose1 becomes resolve after it becomes resolve.
The first conclusion that can be drawn from the above code is:
(1) : Each promise’s state changes not immediately, but at some point in the future. Here’s what comes to mind: when we implement ourselves, we must have a structure that holds all promises together.
(2) : What structure? As you can see,promise2,promise3 are returned by the then method of promise1, so this is a one-to-many relationship, so this must be a tree.
(3) : When to ‘load’ each promise and related event? Very simple,then and catch methods.
(4) : When to ‘implement’ a promise state change, associated event callback? Resolve to reject it.
(5) : Then,catch, resolve,reject
Start coding
When implementing something, such as a promise, the first thing to do is to familiarize yourself with the syntax and features of a promise. Analyze the relationship between each promise before determining an appropriate data structure to store it. If the initial structure relationships are properly designed, the code will be easy to write.
Function PP(){let promises = new Map(); // Store all promise instances let index = 1; // Promise constructor function App(fn){this.state = "pendding"; this.id = +index; // Unique identifier for each promise fn(this.resolve.bind(this), this.reject. Bind (this)); } return App; } the code is simple and unexplainedCopy the code
As mentioned earlier, the implementation of a promise is actually a two-step process, loading and executing. Let’s start with the loading process, which is the implementation of then() catch()
App.prototype. Then = function(resolve, reject){let instance = new App(()=>{}); // Generate an initial state of the promise, and return // to store instance and the corresponding callback. */ let item = {type: 'then', instance: instance, callback: length > 1? ([{ status : 'resolve', fn : resolveFn },{ status : 'reject', fn : rejectFn }]) : ([{ status : 'resolve', fn : ResolveFn}])} // The relationship between two promises is related by the _id of the promise. if(p_item = promises.get(this._id)){ p_item.push(item); }else{ promises.set(this._id,[item]) } return instance; } app.prototype. Catch = function(rejectFn){let instance = new App (()=>{}); let item = { type : 'catch', instance : instance, callback : ([{ status : 'reject', fn : rejectFn }]) } let p_item; if(p_item=promises.get(this._id)){ p_item.push(item); }else{ promises.set(this._id,[item]) } return instance; }Copy the code
Say the implementation of resolve(), reject(). Auxiliary case: Code 2-1
promise1 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("resolve data from promise1");
},1000)
})
promise2 = promise1.then((data) => {
console.log('promise2---', data);
return 'promise2';
})
promise3 = promise2.then((data) => {
console.log('promise3---', data);
return 'promise3';
})
promise4 = promise1.then((data) => {
console.log('promise4---', data);
return 'promise2';
})
promise5 = promise1.catch((data) => {
console.log('promise4---', data);
return 'promise2';
})
Copy the code
This is a diagram of the relationship between codes 2-1,promise
Execution: The resolve method is executed one second later, and the current PROMISE state changes to resolve. After take out with the following three promise1 promise, promise2, promise4, promise5. Then pull out each promise related event and execute it. The above said, like promise2 promise4, promise5 these generated by then or catch promise, state change process determined by the return value.
App.prototype.resolve = function(data){ let ans = null; // the result of the callback function let promise = null; // The promise instance in each child node let items; SetTimeout (fn,0) is used instead of setTimeout(() => {// This is done to handle the promise changes. if(typeof data == 'object' && data! ==null && data.__proto__.constructor == app){ // If the parameter passed in is a Promise object, the state of the current promise does not change immediately, but depends on the change of the incoming promise, that is, data. // All I need to do is associate these two promises. Here I use the linked list data.previous = this; SetTimeout (() => {this.state = "resolve";}else{// In case 1, resolve is passed as a non-promise, in which case the current promise changes immediately and executes the associated event callback. this.value = data; loadLineToList(this); / / (it is important to explain 2) / / out of the current node all child nodes below the if (the items = promise. Get (enclosing _id)) {/ / 2-1 sample code here, for example, take out promise2 respectively, promise4, promise5. / / The data structures in the above promise items are type fields, instance fields, and callback fields. In then, or catch, there is a callback that says 😊 // fetch each promise and execute for(let I =0; i<items.length; i++){ if(items[i].type == 'then'){ try{ ans = items[i].callback[0].fn(data); }catch(err){ promise = promises.get(this._id)[i].instance; promise.reject(err); continue; } if(typeof ans == 'object' &&ans! ==null && ans.__proto__.constructor == app){ ans.previous = promise; }else{ if(promise){ promise.resolve(ans); }}}}else{// There is no node below, exit return; }},0)}},0)}Copy the code
Code 2-2 returns non-promise values, as described above. Let’s move on to another case where the return value is a promise, and that’s what loadLineToList() is used to handle
promise1 = new Promise((resolve, reject) => { setTimeout(()=>{ promise2 = new Promise((resolve) => { setTimeout(() => { promise5 = new Promise((resolve) => { resolve('promise5'); }) promise7 = promise5.then(() => { }) resolve(promise5); },1000) }) console.log('1s'); resolve(promise2); },1000) }) promise3 = promise1.then((data) => { console.log(data); promise4 = new Promise((resolve) => { setTimeout(() => { resolve('promise4'); },1000) }) return promise4; }) promise6 = promise3.then((data) => { console.log(data); }) setTimeout(() => { console.log('--promise1--', promise1); console.log('--promise2--', promise2); console.log('--promise3--', promise3); console.log('--promise4--', promise4); console.log('--promise5--', promise5); console.log('--promise6--', promise6); }, 4000).Copy the code
Promise1 depends on promise2, and the state of promise2 depends on promise5. Similarly, the state of promise3 depends on promise4. It is clear that the relationship between promises is one-way, 1-to-1, so a linked list is appropriate.
App.prototype.then code in data.previous = this; ans.previous = promise; To build a linked list. LoadLineToList the loadLineToList function is used to handle relationships in the linked list before promises. Keep promise1 promise2, promise5 state is consistent, and the promise2 promise5 below all of the promise ‘move’ to promise1 below.
The realization of the reject
Before I say reject, let me explain the catch mechanism of promises.
promise1 = new Promise((resolve, reject) => {
reject('promise1');
})
promise2 = promise1.then(() => {
});
promise4 = promise1.then(() => {
});
promise3 = promise2.catch(() => {
})
Copy the code
This code will report an Uncaught Error: (in promise) promise1, and 2 Uncaught errors: (in promise) promise1 if there is no catch of the last promise3.
The relationship between promises is a tree. When a node becomes reject, there must be a node on the line below the node to catch the reject, or an error will be reported. When promise1 becomes reject, it ‘diverges’ to the child node. When promise2 has no catch, it becomes reject and continues to hunt. The other route, promise4, has no catch, which changes to reject. There is no catch, so there is no catchUncaught Error: (in promise) promise1
With catch out of the way, it’s easy to write the Reject.
App.prototype.reject = function(error){ let promise = null; // let fn = null; // Then or catch the callback function setTimeout(() => {this.state = "reject"; this.value = error; loadLineToList(this); let list = promises.get(this._id); If (! list || list.length==0){ throw new Error("(in promise) "+error); } for(let i=0; i<list.length; i++){ promise = list[i].instance; Type = list[I].type; If (type == 'then'){// This promise is made with p1.then(), but p1 is reject, so the promise is converted to reject // then has two callbacks. The first callback is equivalent to catch if(list[I].callback.length == 1){promise.value = error; promise.reject(error); continue; }else{ fn = list[i].callback[1].fn; }} // Catch fn if(! fn){ fn = list[i].callback[0].fn; } let ans = null; // The result of the callback function // catch the code in the callback function, if the code fails, the current promise becomes reject try{ans = fn(error); fn = null; }catch(err){ promise.reject(err); continue; } promise.value = ans; if(typeof ans == 'object' && ans! ==null && ans.__proto__.constructor == App){ ans.previous = promise; }else{ if(promise){ promise.resolve(ans); }}}}, 5)}Copy the code
conclusion
In fact, look at the realization of promise, the relationship between each promise is interconnected through the structure of the tree. Implementation is also divided into two processes, loading and execution. Loading is the process of building the tree, the catch and then methods. Execution involves traversing resolve and Reject to find the following nodes, changing the state of each promise, and executing the associated callback function.