Write Promise from zero
- In this era of rapid front-end development, if you don't work hard today, you will become garbage tomorrow. Although this is seen on the expression package, I'm sure everyone has seen it. To be honest, this is rough, but this is the case.
- To get back to business, let's take a look at today's content.
- Last time we saw the key point of js is also the difficulty "asynchronous". Strike while the iron is hot. Today we look at the zero handwriting Promise
Let's look at the code first
First, let's review the usage of Promise
new Promise((resolve,reject)=>{ resolve('correct') }).then(value =>{ console.log(value); })
- It can be seen that it is not difficult to print out the "correct" Promise usage in the browser. We just need to know that new Promise passes function(resolve, reject) to Promise, and then do what needs to be done in the function. When necessary, we can modify the constructor Then (RES = > {}) to obtain the corresponding value
Now let's analyze how Promise should be implemented
- First, we should define a Promise structure
- This paper considers the way of es5
(function (window) { function MyPromise(executor) { // 1 define MyPromise constructor function resolve(value) { // Define resolve } function reject(reason) { // Define reject } MyPromise.prototype.then = function (onResolved,onRejected) { // Define then } MyPromise.prototype.catch = function (error) { // Define catch } // Define the execution of the actuator executor(resolve,reject); } window.MyPromise = MyPromise; // 2 Export })(window)
- Then you can see the following implementation
(function (window) { // Promise function Mypromise(executor) { const self = this; // You need to define this point self.status = 'pennding'; // Initialization status self.data = undefined; // The promise object specifies a to store the result data self.callbacks = []; // Structure of each element {onResolved() {}, onRejected() {} function resolve(value) { // resolve if (self.status !== 'pennding') { // Because promise status can only be modified once return; } self.status = 'resolve'; // Change to resolve self.data = value; // Save the value of value if (self.callbacks.length > 0) { // If there is a callback function to be executed, immediately execute the callback function onResolved setTimeout(() => { // The current scheme is to hang the task in the queue and create asynchrony self.callbacks.forEach(callbacksObj => { callbacksObj.onResolved(value) }) },0) } } function reject(reason) { //reject if (self.status !== 'pennding') { // Because promise status can only be modified once return; } self.status = 'reject'; // Change to reject self.data = reason; // Save the value of reason if (self.callbacks.length > 0) { // If there is a callback function to be executed, immediately execute the callback function onRejected setTimeout(() => { self.callbacks.forEach(callbacksObj => { callbacksObj.onRejected(reason) }) },0) } } try { // If the actuator throws an exception executor(resolve, reject); } catch (error) { reject(error) } } // Promise.then() Mypromise.prototype.then = function (onResolved, onRejected) { // Suppose the current status is still pending const self = this; self.callbacks.push({ onResolved, onRejected }) } // Promise.catch() Mypromise.prototype.carch = function (error) { } window.Mypromise = Mypromise; })(window);
<body> <script src="./promise.js"></script> <script> const p = new Mypromise((resolve, reject) => { setTimeout(() => { // Because splitting then has not been processed yet, p.then needs to be executed first resolve(1) console.log("I'll go first") }, 100) }) p.then(value => { console.log("onResolve()1", value) }, reason => { console.l("onReject()1", reason) }) p.then(value => { console.log("onResolve()2", value) }, reason => { console.l("onReject()2", reason) }) </script> </body>
- It can be seen that the execution structure is asynchronous, and the first step is successful.
Let's explain the above code,
- 1. The first is written through es5, so we need to export. We use closures
- 2. Create Promise constructor and export
- 3. When we use promise, we all know to pass in a function with resolve and reject, and perform the required operations in the function,
So the accepted parameter is called the executor
- 4. Two functions are executed in the actuator
- 5. We all know that promise has three states, including pending during initialization
- 6. Change the status and value according to resolve and reject
- 7. We all know the promise constructor, then method and catch method
- 8. Don't forget that promise can also throw Error, so the executor needs to be modified
- 9. Finally, call and execute to see the result
<body> <script src="./promise1.js"></script> <script> const p = new MyPromise((resolve, reject) => { setTimeout(() => { // Due to the reason of splitting, then has not been processed accordingly, and the state cannot be changed at this time resolve(1) console.log("I'll go first") }, 100) }) p.then(value => { console.log("onResolve()1", value) }, reason => { console.l("onReject()1", reason) }) p.then(value => { console.log("onResolve()2", value) }, reason => { console.l("onReject()2", reason) }) </script> </body>
- How did it turn out wrong
- Let's take a look again. Because the callback directly executed is executed synchronously, the task needs to be put into the queue
- At this point, the result is correct.
- Finally, don't forget that promise status can only be modified once
- OK, the above is the simple version of Promise, which does not include the implementation of then.
Next, we upgraded then and the whole based on the above
- The code is as follows
(function (window) { const PENDDING = 'pendding'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { // Define MyPromises constructor const self = this; self.status = PENDDING; self.data = undefined; self.callbacks = []; function resolve(value) { if (self.status === PENDDING) { self.status = FULFILLED; // Change MyPromise status self.data = value; // The value of MyPromise changes accordingly setTimeout(() => { // Asynchronous execution self.callbacks.forEach(callbacksObj => { // If the callback function is to be executed, execute the callback immediately callbacksObj.onResolved(value) }) }) } } function reject(reason) { if (self.status === PENDDING) { self.status = REJECTED; self.data = reason; setTimeout(() => { self.callbacks.forEach(callbacksObj => { callbacksObj.onRejected(reason); }); }) } } try { // MyPromise can throw exceptions executor(resolve, reject); } catch (error) { reject(error) } } /* MyPromise then() of prototype object Specify success and failure callbacks Returns a new callback function // The returned MyPromise result is determined by the result of onResolved/onRejected */ MyPromise.prototype.then = function (onResolved, onRejected) { // Define then const self = this; // Specifies the default value of the callback function (must be a function) onResolved = typeof onResolved==='function' ? onResolved : value => value; onRejected = typeof onRejected==='function' ? onRejected : reason => {throw reason}; return new MyPromise((resolve,reject)=>{ // Returns a new MyPromise object function handle(callback) { // The returned MyPromise result is determined by the result of onResolved/onRejected // 1. Throw exception MyPromise, the result is failure, and the reason is the result // 2. The returned result is mypromise. Mypromise is the current result // 3. The result returned is not mypromise value // You need to capture to know whether there is an exception try{ const result = callback(self.data) // Determine whether it is MyPromise if ( result instanceof MyPromise){ // Only then knows the result result.then(value=>resolve(value),reason=>reject(reason)) }else{ resolve(result) } }catch(error){ reject(error) // The returned result is reject(error), the first point above } } // Judge the current status if (self.status === FULFILLED){ // The status is full setTimeout(()=>{ // Execute asynchronous callback immediately handle(onResolved); }) } else if (self.status === REJECTED){ // The status is rejected setTimeout(()=>{ // Execute asynchronous callback immediately handle(onRejected); }) }else{ // Pending saves success and failure to the callbacks for caching self.callbacks.push({ onResolved(value){ //The callback function is called in the function, and the result of MyPromise is changed according to the result of the callback function handle(onResolved) //Why is there no setTimeout here, because it has been written above that after changing the state, go back to the callbacks to loop the callback function to be executed }, onRejected(reason){ handle(onRejected) } }) } }) } MyPromise.prototype.catch = function (onRejected) { //Define then return this.then(undefined,onRejected) } window.MyPromise = MyPromise; // Export MyPromise })(window)
- I believe you see this code and I have the same reaction. If I go, why is it different from before
- In fact, when I started to implement the handwritten Promise, I think it's really not difficult. If it's difficult to say that the implementation of then is a little more difficult than the above.
- OK, let me explain next
- 1. Firstly, some constants are defined on the basis of the original ones, which is convenient to use
- 2. You know promise What does then return
- 3. Then judge the status
- 4. OK, next, let's operate the corresponding state
- 5. We can see that there is no timer added to the pending state. Why not asynchronous? Because it is stored in callbacks, and then the loop judgment will be carried out when it is executed to the corresponding position, and it is asynchronous during the loop operation. Improve the pending
- 6. From the previous code, we can see that there are four locations with high code similarity, so define a function and encapsulate it (note that it should be in new Promise)
- 7. Set the default value again
- 8. Let's improve catch
Next, let's test
- There seems to be no problem
- Try catch again
- The novice's first writing is still not very good, but each step is really knocked out through how he learns. I hope to help friends who want to learn promise as well as me. It's hard to write it directly from the beginning to the end. In fact, it takes a lot of time, but it receives a lot of goods.