How you implement a complete Promise
I have used Promise, but I always feel like I don’t understand it. I have read many articles, but I still don’t understand the implementation principle of Promise. Later, I read the articles and debugged the code, and finally I slowly got a feel for it. , let’s implement a Promise according to our own understanding.
To fully understand the code, you need to understand the meaning of this and closure .
What is Promise
Simply put, Promise is mainly to solve the problem of asynchronous callbacks. Using Promise to handle asynchronous callbacks makes the code hierarchy clear, easy to understand, and easier to maintain. Its mainstream specifications are currently mainly Promises/A+. If you are not familiar with the usage of Promise, you can refer to my article - es6 study notes 5--promise. If you understand it, then read this article, it will be of great help to you.
Before we begin, let’s first write a promise application scenario to experience the role of promise. Google and Firefox currently support es6 promises. We use setTimeout to simulate asynchronous operation. The specific code is as follows:
function fn1(resolve, reject) { setTimeout(function() { console.log('步骤一:执行'); resolve('1'); },500); } function fn2(resolve, reject) { setTimeout(function() { console.log('步骤二:执行'); resolve('2'); },100); } new Promise(fn1).then(function(val){ console.log(val); return new Promise(fn2); }).then(function(val){ console.log(val); return 33; }).then(function(val){ console.log(val); });
In the end, the promise we wrote can also achieve this function.
Initial construction
Let’s write a simple promsie. The parameter of Promise is function fn. Pass the internally defined resolve method as a parameter to fn and call fn. When the asynchronous operation is successful, the resolve method will be called, and then the callback function registered in then will be executed.
function Promise(fn){ //需要一个成功时的回调 var callback; //一个实例的方法,用来注册异步事件 this.then = function(done){ callback = done; } function resolve(){ callback(); } fn(resolve); }
Add chaining support
Add chaining below, and the successful callback method must be converted into an array before it can be stored. At the same time, we add parameters to the resolve method so that undefined will not be output.
function Promise(fn) { var promise = this, value = null; promise._resolves = []; this.then = function (onFulfilled) { promise._resolves.push(onFulfilled); return this; }; function resolve(value) { promise._resolves.forEach(function (callback) { callback(value); }); } fn(resolve); }
promise = this, so we don’t have to worry about the sudden change of this pointer at some point.
Call the then method and put the callback into the promise._resolves queue;
While creating the Promise object, call its fn and pass in the resolve method . When the asynchronous operation of fn is successfully executed, resolve will be called, that is,
<span style="color:#000000">promise._resolves# will be executed. </span>## Callback in the
column;##resolve method
<span style="color:#000000">Receives one parameter</span>
, which is the result returned by asynchronous operation, convenient for value transfer.- The return this in the then method implements chain calls.
In order to solve this problem, we can add setTimeout in resolve to place the logic of callback execution in
resolve to the end of the JS task queue. function resolve(value) {
setTimeout(function() {
promise._resolves.forEach(function (callback) {
callback(value);
});
},0);
}
Analysis of the basics of Promise There is a problem here: if the Promise asynchronous operation has succeeded, the callback registered by then will never be executed again, which is not in line with our expectations.
I don’t quite understand this sentence. If you know, you can leave a message. It’s best to give an example. But I personally think that the registered callbacks in then will be added to the array before resolve is run, so there will be no non-execution.
Follow the above steps and introduce states:
<p style="margin-bottom: 7px;">function Promise(fn) {<br/> var promise = this,<br/> value = null;<br/> promise._resolves = [];<br/> promise._status = 'PENDING';<br/><br/> this.then = function (onFulfilled) {<br/> if (promise._status === 'PENDING') {<br/> promise._resolves.push(onFulfilled);<br/> return this;<br/> }<br/> onFulfilled(value);<br/> return this;<br/> };<br/> function resolve(value) {<br/> setTimeout(function(){<br/> promise._status = "FULFILLED";<br/> promise._resolves.forEach(function (callback) {<br/> callback(value);<br/> })<br/> },0);<br/> }<br/><br/> fn(resolve);<br/>}<br/></p>
Each Promise has three mutually exclusive states: pending, fulfilled, and rejected. There are only two possibilities for the state of a Promise object to change: from pending to fulfilled and from pending to rejected. As long as these two situations occur, the state will be solidified and will not change again, and will maintain this result. Even if the change has already occurred, if you add a callback function to the Promise object, you will get the result immediately. This is completely different from an event. The characteristic of an event is that if you miss it and listen again, you will not get the result.
Add the delivery of asynchronous results
The current writing method does not consider the delivery of asynchronously returned results. Let’s add the delivery of results:
function resolve(value) { setTimeout(function(){ promise._status = "FULFILLED"; promise._resolves.forEach(function (callback) { value = callback(value); }) },0); }
Serial Promise
Serial Promise means that after the current promise reaches the fulfilled state, the next promise (the next promise) starts. For example, we first use ajax to obtain the user's data from the background, and then obtain other data based on this data.
Here we mainly modify the then method:
this.then = function (onFulfilled) { return new Promise(function(resolve) { function handle(value) { var ret = isFunction(onFulfilled) && onFulfilled(value) || value; resolve(ret); } if (promise._status === 'PENDING') { promise._resolves.push(handle); } else if(promise._status === FULFILLED){ handle(value); } }) };
The then method should be changed a lot. Let me explain it here:
注意的是,new Promise() 中匿名函数中的 promise (promise._resolves中的 promise)指向的都是上一个 promise 对象, 而不是当前这个刚刚创建的。
首先我们返回的是新的一个promise对象,因为是同类型,所以链式仍然可以实现。
其次,我们添加了一个 handle 函数,handle 函数对上一个 promise 的 then 中回调进行了处理,并且调用了当前的 promise 中的 resolve 方法。
接着将 handle 函数添加到 上一个promise 的 promise._resolves 中,当异步操作成功后就会执行 handle 函数,这样就可以 执行 当前 promise 对象的回调方法。我们的目的就达到了。
有些人在这里可能会有点犯晕,有必要对执行过程分析一下,具体参看以下代码:
new Promise(fn1).then(fn2).then(fn3)})
fn1, fn2, fn3的函数具体可参看最前面的定义。
首先我们创建了一个 Promise 实例,这里叫做 promise1;接着会运行 fn1(resolve);
但是 fn1 中有一个 setTimeout 函数,于是就会先跳过这一部分,运行后面的第一个 then 方法;
then 返回一个新的对象 promise2, promise2 对象的 resolve方法和 then 方法的中回调函数 fn2 都被封装在 handle 中, 然后 handle 被添加到 promise1._resolves 数组中。
接着运行第二个 then 方法,同样返回一个新的对象 promise3, 包含 promise3 的 resolve 方法和回调函数 fn3 的 handle 方法被添加到 promise2._resolves 数组中。
到此两个 then 运行结束。 setTimeout 中的延迟时间一到,就会调用 promise1的 resolve方法。
resolve 方法的执行,会调用 promise1._resolves 数组中的回调,之前我们添加的 handle 方法就会被执行; 也就是 fn2 和 promsie2 的 resolve 方法,都被调用了。
以此类推,fn3 会和promise3 的 resolve 方法 一起执行,因为后面没有 then 方法了,promise3._resolves 数组是空的 。
至此所有回调执行结束
但这里还存在一个问题,就是我们的 then 里面函数不能对 Promise 对象进行处理。这里我们需要再次对 then 进行修改,使其能够处理 promise 对象。
this.then = function (onFulfilled) { return new Promise(function(resolve) { function handle(value) { var ret = typeof onFulfilled === 'function' && onFulfilled(value) || value; if( ret && typeof ret ['then'] == 'function'){ ret.then(function(value){ resolve(value); }); } else { resolve(ret); } } if (promise._status === 'PENDING') { promise._resolves.push(handle); } else if(promise._status === FULFILLED){ handle(value); } }) };
在 then 方法里面,我们对 ret 进行了判断,如果是一个 promise 对象,就会调用其 then 方法,形成一个嵌套,直到其不是promise对象为止。同时 在 then 方法中我们添加了调用 resolve 方法,这样链式得以维持。
失败处理
异步操作不可能都成功,在异步操作失败时,标记其状态为 rejected,并执行注册的失败回调。
有了之前处理 fulfilled 状态的经验,支持错误处理变得很容易。毫无疑问的是,在注册回调、处理状态变更上都要加入新的逻辑:
this.then = function (onFulfilled, onRejected) { return new Promise(function(resolve, reject) { function handle(value) { ....... } function errback(reason){ reason = isFunction(onRejected) && onRejected(reason) || reason; reject(reason); } if (promise._status === 'PENDING') { promise._resolves.push(handle); promise._rejects.push(errback); } else if(promise._status === 'FULFILLED'){ handle(value); } else if(promise._status === 'REJECTED') { errback(promise._reason); } }) }; function reject(value) { setTimeout(function(){ promise._status = "REJECTED"; promise._rejects.forEach(function (callback) { promise._reason = callback( value); }) },0); }
添加Promise.all方法
Promise.all 可以接收一个元素为 Promise 对象的数组作为参数,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回。
具体代码如下:
Promise.all = function(promises){ if (!Array.isArray(promises)) { throw new TypeError('You must pass an array to all.'); } // 返回一个promise 实例 return new Promise(function(resolve,reject){ var i = 0, result = [], len = promises.length, count = len; // 每一个 promise 执行成功后,就会调用一次 resolve 函数 function resolver(index) { return function(value) { resolveAll(index, value); }; } function rejecter(reason){ reject(reason); } function resolveAll(index,value){ // 存储每一个promise的参数 result[index] = value; // 等于0 表明所有的promise 都已经运行完成,执行resolve函数 if( --count == 0){ resolve(result) } } // 依次循环执行每个promise for (; i < len; i++) { // 若有一个失败,就执行rejecter函数 promises[i].then(resolver(i),rejecter); } }); }
Promise.all会返回一个 Promise 实例,该实例直到参数中的所有的 promise 都执行成功,才会执行成功回调,一个失败就会执行失败回调。
日常开发中经常会遇到这样的需求,在不同的接口请求数据然后拼合成自己所需的数据,通常这些接口之间没有关联(例如不需要前一个接口的数据作为后一个接口的参数),这个时候 Promise.all 方法就可以派上用场了。
添加Promise.race方法
该函数和 Promise.all 相类似,它同样接收一个数组,不同的是只要该数组中的任意一个 Promise 对象的状态发生变化(无论是 resolve 还是 reject)该方法都会返回。我们只需要对 Promise.all 方法稍加修改就可以了。
Promise.race = function(promises){ if (!Array.isArray(promises)) { throw new TypeError('You must pass an array to race.'); } return Promise(function(resolve,reject){ var i = 0, len = promises.length; function resolver(value) { resolve(value); } function rejecter(reason){ reject(reason); } for (; i < len; i++) { promises[i].then(resolver,rejecter); } }); }
代码中没有类似一个 resolveAll 的函数,因为我们不需要等待所有的 promise 对象状态都发生变化,只要一个就可以了。
添加其他API以及封装函数
到这里,Promise 的主要API都已经完成了,另外我们在添加一些比较常见的方法。也对一些可能出现的错误进行了处理,最后对其进行封装。
完整的代码如下:
(function(window,undefined){ // resolve 和 reject 最终都会调用该函数 var final = function(status,value){ var promise = this, fn, st; if(promise._status !== 'PENDING') return; // 所以的执行都是异步调用,保证then是先执行的 setTimeout(function(){ promise._status = status; st = promise._status === 'FULFILLED' queue = promise[st ? '_resolves' : '_rejects']; while(fn = queue.shift()) { value = fn.call(promise, value) || value; } promise[st ? '_value' : '_reason'] = value; promise['_resolves'] = promise['_rejects'] = undefined; }); } //参数是一个函数,内部提供两个函数作为该函数的参数,分别是resolve 和 reject var Promise = function(resolver){ if (!(typeof resolver === 'function' )) throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); //如果不是promise实例,就new一个 if(!(this instanceof Promise)) return new Promise(resolver); var promise = this; promise._value; promise._reason; promise._status = 'PENDING'; //存储状态 promise._resolves = []; promise._rejects = []; // var resolve = function(value) { //由於apply參數是數組 final.apply(promise,['FULFILLED'].concat([value])); } var reject = function(reason){ final.apply(promise,['REJECTED'].concat([reason])); } resolver(resolve,reject); } Promise.prototype.then = function(onFulfilled,onRejected){ var promise = this; // 每次返回一个promise,保证是可thenable的 return new Promise(function(resolve,reject){ function handle(value) { // 這一步很關鍵,只有這樣才可以將值傳遞給下一個resolve var ret = typeof onFulfilled === 'function' && onFulfilled(value) || value; //判断是不是promise 对象 if (ret && typeof ret ['then'] == 'function') { ret.then(function(value) { resolve(value); }, function(reason) { reject(reason); }); } else { resolve(ret); } } function errback(reason){ reason = typeof onRejected === 'function' && onRejected(reason) || reason; reject(reason); } if(promise._status === 'PENDING'){ promise._resolves.push(handle); promise._rejects.push(errback); }else if(promise._status === FULFILLED){ // 状态改变后的then操作,立刻执行 callback(promise._value); }else if(promise._status === REJECTED){ errback(promise._reason); } }); } Promise.prototype.catch = function(onRejected){ return this.then(undefined, onRejected) } Promise.prototype.delay = function(ms,value){ return this.then(function(ori){ return Promise.delay(ms,value || ori); }) } Promise.delay = function(ms,value){ return new Promise(function(resolve,reject){ setTimeout(function(){ resolve(value); console.log('1'); },ms); }) } Promise.resolve = function(arg){ return new Promise(function(resolve,reject){ resolve(arg) }) } Promise.reject = function(arg){ return Promise(function(resolve,reject){ reject(arg) }) } Promise.all = function(promises){ if (!Array.isArray(promises)) { throw new TypeError('You must pass an array to all.'); } return Promise(function(resolve,reject){ var i = 0, result = [], len = promises.length, count = len //这里与race中的函数相比,多了一层嵌套,要传入index function resolver(index) { return function(value) { resolveAll(index, value); }; } function rejecter(reason){ reject(reason); } function resolveAll(index,value){ result[index] = value; if( --count == 0){ resolve(result) } } for (; i < len; i++) { promises[i].then(resolver(i),rejecter); } }); } Promise.race = function(promises){ if (!Array.isArray(promises)) { throw new TypeError('You must pass an array to race.'); } return Promise(function(resolve,reject){ var i = 0, len = promises.length; function resolver(value) { resolve(value); } function rejecter(reason){ reject(reason); } for (; i < len; i++) { promises[i].then(resolver,rejecter); } }); } window.Promise = Promise; })(window);
代码写完了,总要写几个实例看看效果啊,具体看下面的测试代码:
var getData100 = function(){ return new Promise(function(resolve,reject){ setTimeout(function(){ resolve('100ms'); },1000); }); } var getData200 = function(){ return new Promise(function(resolve,reject){ setTimeout(function(){ resolve('200ms'); },2000); }); } var getData300 = function(){ return new Promise(function(resolve,reject){ setTimeout(function(){ reject('reject'); },3000); }); } getData100().then(function(data){ console.log(data); // 100ms return getData200(); }).then(function(data){ console.log(data); // 200ms return getData300(); }).then(function(data){ console.log(data); }, function(data){ console.log(data); // 'reject' }); Promise.all([getData100(), getData200()]).then(function(data){ console.log(data); // [ "100ms", "200ms" ] }); Promise.race([getData100(), getData200(), getData300()]).then(function(data){ console.log(data); // 100ms }); Promise.resolve('resolve').then(function(data){ console.log(data); //'resolve' }) Promise.reject('reject函数').then(function(data){ console.log(data); }, function(data){ console.log(data); //'reject函数' })
The above is the detailed content of How you implement a complete Promise. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



WeChat is one of the mainstream chat tools. We can meet new friends, contact old friends and maintain the friendship between friends through WeChat. Just as there is no such thing as a banquet that never ends, disagreements will inevitably occur when people get along with each other. When a person extremely affects your mood, or you find that your views are inconsistent when you get along, and you can no longer communicate, then we may need to delete WeChat friends. How to delete WeChat friends? The first step to delete WeChat friends: tap [Address Book] on the main WeChat interface; the second step: click on the friend you want to delete and enter [Details]; the third step: click [...] in the upper right corner; Step 4: Click [Delete] below; Step 5: After understanding the page prompts, click [Delete Contact]; Warm

Tomato Novel is a very popular novel reading software. We often have new novels and comics to read in Tomato Novel. Every novel and comic is very interesting. Many friends also want to write novels. Earn pocket money and edit the content of the novel you want to write into text. So how do we write the novel in it? My friends don’t know, so let’s go to this site together. Let’s take some time to look at an introduction to how to write a novel. Share the Tomato novel tutorial on how to write a novel. 1. First open the Tomato free novel app on your mobile phone and click on Personal Center - Writer Center. 2. Jump to the Tomato Writer Assistant page - click on Create a new book at the end of the novel.

Colorful motherboards enjoy high popularity and market share in the Chinese domestic market, but some users of Colorful motherboards still don’t know how to enter the bios for settings? In response to this situation, the editor has specially brought you two methods to enter the colorful motherboard bios. Come and try it! Method 1: Use the U disk startup shortcut key to directly enter the U disk installation system. The shortcut key for the Colorful motherboard to start the U disk with one click is ESC or F11. First, use Black Shark Installation Master to create a Black Shark U disk boot disk, and then turn on the computer. When you see the startup screen, continuously press the ESC or F11 key on the keyboard to enter a window for sequential selection of startup items. Move the cursor to the place where "USB" is displayed, and then

Unfortunately, people often delete certain contacts accidentally for some reasons. WeChat is a widely used social software. To help users solve this problem, this article will introduce how to retrieve deleted contacts in a simple way. 1. Understand the WeChat contact deletion mechanism. This provides us with the possibility to retrieve deleted contacts. The contact deletion mechanism in WeChat removes them from the address book, but does not delete them completely. 2. Use WeChat’s built-in “Contact Book Recovery” function. WeChat provides “Contact Book Recovery” to save time and energy. Users can quickly retrieve previously deleted contacts through this function. 3. Enter the WeChat settings page and click the lower right corner, open the WeChat application "Me" and click the settings icon in the upper right corner to enter the settings page.

Setting font size has become an important personalization requirement as mobile phones become an important tool in people's daily lives. In order to meet the needs of different users, this article will introduce how to improve the mobile phone use experience and adjust the font size of the mobile phone through simple operations. Why do you need to adjust the font size of your mobile phone - Adjusting the font size can make the text clearer and easier to read - Suitable for the reading needs of users of different ages - Convenient for users with poor vision to use the font size setting function of the mobile phone system - How to enter the system settings interface - In Find and enter the "Display" option in the settings interface - find the "Font Size" option and adjust it. Adjust the font size with a third-party application - download and install an application that supports font size adjustment - open the application and enter the relevant settings interface - according to the individual

A summary of how to obtain Win11 administrator rights. In the Windows 11 operating system, administrator rights are one of the very important permissions that allow users to perform various operations on the system. Sometimes, we may need to obtain administrator rights to complete some operations, such as installing software, modifying system settings, etc. The following summarizes some methods for obtaining Win11 administrator rights, I hope it can help you. 1. Use shortcut keys. In Windows 11 system, you can quickly open the command prompt through shortcut keys.

Mobile games have become an integral part of people's lives with the development of technology. It has attracted the attention of many players with its cute dragon egg image and interesting hatching process, and one of the games that has attracted much attention is the mobile version of Dragon Egg. To help players better cultivate and grow their own dragons in the game, this article will introduce to you how to hatch dragon eggs in the mobile version. 1. Choose the appropriate type of dragon egg. Players need to carefully choose the type of dragon egg that they like and suit themselves, based on the different types of dragon egg attributes and abilities provided in the game. 2. Upgrade the level of the incubation machine. Players need to improve the level of the incubation machine by completing tasks and collecting props. The level of the incubation machine determines the hatching speed and hatching success rate. 3. Collect the resources required for hatching. Players need to be in the game

Detailed explanation of Oracle version query method Oracle is one of the most popular relational database management systems in the world. It provides rich functions and powerful performance and is widely used in enterprises. In the process of database management and development, it is very important to understand the version of the Oracle database. This article will introduce in detail how to query the version information of the Oracle database and give specific code examples. Query the database version of the SQL statement in the Oracle database by executing a simple SQL statement
