什么是Promise?
Promise 是异步编程的一种解决方案,ES6将其写进了语言标准,原生提供了Promise对象。
Promise主要解决了什么问题?
- 消灭嵌套调用:通过 Promise 的链式调用可以解决回调地狱;
- 合并多个任务的请求结果:使用 Promise.all 获取合并多个任务的错误处理
从零开始手写一个Promise
手写一个Promise需要遵循 Promise/A+规范
首先,我们从Promise最基础的概念开始:
- Promise有三种状态,
pending,fulfilled,rejected,状态只能从pending到fulfilled或rejected,一旦确定无法改变; - Promise有一个
value保存成功状态的值,一个reason保存失败状态的值; - new Promise的时候传入一个
executor立即执行函数,函数接收resolve和reject两个函数参数. 若是executor函数报错 直接执行reject(); resolve()将Promise状态变为fulfilled,reject()将Promise状态变为rejected,并执行then()传入的回调函数;- Promise有一个
then函数, 依次传入onFulfilled和onRejected回调函数,
当status为fulfilled时执行onFulfilled回调函数,传入this.value。当status为rejected时执行onRejected函数,传入this.reason;
按照上面的概念,我们可以写出一个最基本的Promise
constPENDING="pending";constFULFILLED="fulfilled";constREJECTED="rejected";classMyPromise{constructor(executor){this.status=PENDING;this.value=undefined;this.reason=undefined;// 用箭头函数确保this指向当前实例对象letresolve=(value)=>{if(this.status===PENDING){this.status=FULFILLED;this.value=value;}}letreject=(value)=>{if(this.status===PENDING){this.status=REJECTED;this.reason=value;}}try{executor(resolve,reject);}catch(error){reject(error);}}then(onFulfilled,onRejected){if(this.status===FULFILLED){onFulfilled(this.value);}if(this.status===REJECTED){onRejected(this.reason);}}}写完代码测试一下
constpromise=newMyPromise((resolve,reject)=>{resolve('成功');}).then((data)=>{console.log('success',data)},(err)=>{console.log('failed',err)})浏览器打印出'success 成功'
但这时候只是同步操作的Promise,若是我们在executor()中传入一个异步操作,则会什么都没有打印出来。
constpromise=newMyPromise((resolve,reject)=>{setTimeout(()=>{resolve('成功');})}).then((data)=>{console.log('success',data)},(err)=>{console.log('failed',err)})这是因为执行then方法时,此时Promise的状态是pending,不会触发回调。那么在pending状态的时候应该怎么做呢?
不考虑异常的情况下,Promise的状态改变只依赖于构造函数中的resolve()函数和reject函数,那么可以考虑将onFulfilled和onRejected回调函数先保存到Promise中我们定义的属性onFulfilledFn和onRejectedFn中,等到状态改变时再调用。
这其实就是一个发布订阅模式,这种收集依赖 -> 触发通知 -> 取出依赖执行的方式,被广泛运用于发布订阅模式的实现
在Promise里,then收集依赖-> 异步触发resolve -> resolve执行依赖。
结合以上思路,优化一下代码:
constPENDING="pending";constFULFILLED="fulfilled";constREJECTED="rejected";classMyPromise{constructor(executor){this.status=PENDING;this.value=undefined;this.reason=undefined;this.onFulfilledFn=[];this.onRejectedFn=[];letresolve=(value)=>{if(this.status===PENDING){this.status=FULFILLED;this.value=value;this.onFulfilledFn.forEach(fn=>fn());}}letreject=(value)=>{if(this.status===PENDING){this.status=REJECTED;this.reason=value;this.onRejectedFn.forEach(fn=>fn());}}try{executor(resolve,reject);}catch(error){reject(error);}}then(onFulfilled,onRejected){if(this.status===FULFILLED){onFulfilled(this.value);}if(this.status===REJECTED){onRejected(this.reason);}if(this.status===PENDING){this.onFulfilledFn.push(()=>onFulfilled(this.value));this.onRejectedFn.push(()=>onRejected(this.reason));}}}此时测试一下,会发现浏览器延迟1s打印出‘success 成功’
constpromise=newMyPromise((resolve,reject)=>{setTimeout(()=>{resolve('成功');},1000);}).then((data)=>{console.log('success',data)},(err)=>{console.log('failed',err)})then的链式调用
这是Promise实现的重点和难点,我们先来看一下JavaScript中原生Promise对象的then是如何链式调用的
constp1=newPromise((resolve,reject)=>{resolve(1)})p1.then(res=>{console.log(res)// then回调中可以return一个PromisereturnnewPromise((resolve,reject)=>{setTimeout(()=>{resolve(2)},1000);})}).then(res=>{console.log(res)// then回调中也可以return一个值return3}).then(res=>{console.log(res)})浏览器依次输出
1
2
3
这种链式调用该如何实现?
- 为了支持多次调用,then方法需要返回一个新的Promise,规范里称为Promise2。
- then的回调需要顺序执行,虽然上面的代码return了一个Promise,但执行顺序仍保证是1->2->3。因此,如果then返回值是一个Promise实例的话,我们要等待Promise状态变更后,才执行下一个then方法收集的回调,这就要求我们对then返回值x进行分类讨论。
then(onFulfilled,onRejected){letpromise2=newPromise((resolve,reject)=>{
...
if(this.status===PENDING){this.onFulfilledFn.push(()=>{try{// 执行第一个(当前)Promise的成功回调,并获取返回值letx=onFulfilled(this.value);// 分类讨论返回值,如果是Promise实例,那么等待Promise的状态变更后resolve,否则直接resolvexinstanceofMyPromise ? x.then(resolve,reject) : resolve(x);}catch(error){reject(error);}});this.onRejectedFn.push(()=>{try{// 执行第一个(当前)Promise的失败回调,并获取返回值letx=onRejected(this.reason);// 分类讨论返回值,如果是Promise实例,那么等待Promise的状态变更,否则直接resolvexinstanceofMyPromise ? x.then(resolve,reject) : resolve(x);}catch(error){reject(error);}});}})测试一下
constp1=newMyPromise((resolve,reject)=>{setTimeout(()=>{resolve(1)},500);})p1.then(res=>{console.log(res)// then回调中可以return一个PromisereturnnewMyPromise((resolve,reject)=>{setTimeout(()=>{resolve(2)},1000);})}).then(res=>{console.log(res)// then回调中也可以return一个值return3}).then(res=>{console.log(res)})//输出 1 2 3这只是最简单的x值判断,只是单纯判断x是否Promise实例,实际上还需要判断更多复杂情况。所以我们可以优化一下,将判断x的函数命名为resolvePromise提取出来。
resolvePromise的参数有promise2(默认返回的promise)、x(我们自己return的对象)、resolve、reject。
代码如下:
functionresolvePromise(promise2,x,resolve,reject){// 循环引用,自己等待自己完成是错误的实现,用一个类型错误,结束掉 promiseif(promise2===x){returnreject(newTypeError("Channing cycle detected for promise"));}// 防止多次调用letcalled;// x为对象或者函数if((typeofx==="object"&&x!==null)||typeofx==="function"){try{// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)letthen=x.then;if(typeofthen==="function"){// 让then执行,第一个参数是this,后面是成功的回调 和 失败的回调then.call(x,(res)=>{// 成功和失败只能调用一个if(called)return;called=true;// 递归解析的过程(因为可能 promise 中还有 promise)resolvePromise(promise2,res,resolve,reject);},(err)=>{// 成功和失败只能调用一个if(called)return;called=true;reject(err);// 失败了就失败了});}else{resolve(x);}}catch(error){if(called)return;called=true;reject(error);}}else{// 普通值直接resolveresolve(x);}}then(onFulfilled,onRejected){letpromise2=newMyPromise((resolve,reject)=>{if(this.status===FULFILLED){try{// 执行第一个(当前)Promise的成功回调,并获取返回值letx=onFulfilled(this.value);// resolvePromise函数,处理自己return的promise和默认的promise2的关系resolvePromise(promise2,x,resolve,reject);}catch(error){reject(error);}}if(this.status===REJECTED){try{// 执行第一个(当前)Promise的失败回调,并获取返回值letx=onRejected(this.reason);// resolvePromise函数,处理自己return的promise和默认的promise2的关系resolvePromise(promise2,x,resolve,reject);}catch(error){reject(error);}}if(this.status===PENDING){this.onFulfilledFn.push(()=>{try{// 执行第一个(当前)Promise的成功回调,并获取返回值letx=onFulfilled(this.value);// resolvePromise函数,处理自己return的promise和默认的promise2的关系resolvePromise(promise2,x,resolve,reject);}catch(error){reject(error);}});this.onRejectedFn.push(()=>{try{// 执行第一个(当前)Promise的失败回调,并获取返回值letx=onRejected(this.reason);// resolvePromise函数,处理自己return的promise和默认的promise2的关系resolvePromise(promise2,x,resolve,reject);}catch(error){reject(error);}});}})returnpromise2;}then的其它细节处理
- 「值穿透」:根据规范,如果 then() 接收的参数不是function,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值。
- 「异步模拟」:规定onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题。
这样,Promise的主要部分就完成了,完整代码如下:
constPENDING="pending";constFULFILLED="fulfilled";constREJECTED="rejected";functionresolvePromise(promise2,x,resolve,reject){// 循环引用,自己等待自己完成是错误的实现,用一个类型错误,结束掉 promiseif(promise2===x){returnreject(newTypeError("Channing cycle detected for promise"));}// 防止多次调用letcalled;// x为对象或者函数if((typeofx==="object"&&x!==null)||typeofx==="function"){try{// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)letthen=x.then;if(typeofthen==="function"){// 让then执行,第一个参数是this,后面是成功的回调 和 失败的回调then.call(x,(res)=>{// 成功和失败只能调用一个if(called)return;called=true;// 递归解析的过程(因为可能 promise 中还有 promise)resolvePromise(promise2,res,resolve,reject);},(err)=>{// 成功和失败只能调用一个if(called)return;called=true;reject(err);// 失败了就失败了});}else{resolve(x);}}catch(error){if(called)return;called=true;reject(error);}}else{// 普通值直接resolveresolve(x);}}classMyPromise{constructor(executor){this.status=PENDING;this.value=undefined;this.reason=undefined;this.onFulfilledFn=[];this.onRejectedFn=[];letresolve=(value)=>{if(this.status===PENDING){this.status=FULFILLED;this.value=value;this.onFulfilledFn.forEach((fn)=>fn());}};letreject=(value)=>{if(this.status===PENDING){this.status=REJECTED;this.reason=value;this.onRejectedFn.forEach((fn)=>fn());}};try{executor(resolve,reject);}catch(error){reject(error);}}then(onFulfilled,onRejected){// 解决值穿透// onFulfilled如果不是函数,就忽略onFulfilled,直接返回valueonFulfilled=typeofonFulfilled==="function" ? onFulfilled : (val)=>val;// onRejected如果不是函数,就忽略onRejected,直接扔出错误onRejected=typeofonRejected==="function" ? onRejected : (err)=>{throwerr;};letpromise2=newMyPromise((resolve,reject)=>{if(this.status===FULFILLED){// 模拟异步setTimeout(()=>{try{letx=onFulfilled(this.value);resolvePromise(promise2,x,resolve,reject);}catch(e){reject(e);}},0);}if(this.status===REJECTED){setTimeout(()=>{try{letx=onRejected(this.reason);resolvePromise(promise2,x,resolve,reject);}catch(error){reject(error);}},0);}if(this.status===PENDING){this.onFulfilledFn.push(()=>{setTimeout(()=>{try{// 执行第一个(当前)Promise的成功回调,并获取返回值letx=onFulfilled(this.value);// resolvePromise函数,处理自己return的promise和默认的promise2的关系resolvePromise(promise2,x,resolve,reject);}catch(error){reject(error);}},0)});this.onRejectedFn.push(()=>{setTimeout(()=>{try{// 执行第一个(当前)Promise的失败回调,并获取返回值letx=onRejected(this.reason);// resolvePromise函数,处理自己return的promise和默认的promise2的关系resolvePromise(promise2,x,resolve,reject);}catch(error){reject(error);}},0)});}});returnpromise2;}}写完代码测试一下
首先在Promise实现的代码中加入
MyPromise.defer=MyPromise.deferred=function(){varresult={};result.promise=newMyPromise(function(resolve,reject){result.resolve=resolve;result.reject=reject;});returnresult;}安装测试脚本
yarn add promises-aplus-tests --dev
在package.json中配置指令
"test": "promises-aplus-tests js/promise/promise"
npm run test
promises-aplus-tests 中共有 872 条测试用例,代码完美通过

Promise 的 API
原生的Promise还提供了一些方法,我们顺便一起写出来
- Promise.resolve()
- Promise.reject()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.all()
- Promise.race()
- Promise.allSettled()
- Promise.any()
Promise.resolve()
staticresolve(value){// 根据规范, 如果参数是Promise实例, 直接return这个实例if(valueinstanceofMyPromise)returnvalue;returnnewMyPromise((resolve,reject)=>{resolve(value);})}Promise.reject()
staticreject(reason){returnnewMyPromise((resolve,reject)=>{reject(reason);})}Promise.prototype.catch()
MyPromise.prototype.catch=function(onRejected){returnthis.then(null,onRejected);}Promise.prototype.finally()
- .finally()方法不管Promise对象最后的状态如何都会执行传入的回调函数
- .finally()方法的回调函数不接受任何的参数
- 它最终返回的默认会是一个上一次的Promise对象值,如果返回的是成功的 Promise,会采用上一次的结果;如果返回的是失败的 Promise,会用这个失败的结果,传到 catch 中
MyPromise.prototype.finally=function(callback){returnthis.then((value)=>{returnMyPromise.resolve(callback()).then(()=>value);},(reason)=>{returnMyPromise.resolve(callback()).then(()=>{throwreason})})}Promise.all()
Promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)
- 当所有结果成功返回时,按照请求顺序返回成功。当其中有一个失败方法时,则进入失败方法。
- catch()函数能够捕获到.all()里最先的那个异常,注意只会捕获最先异常的那次,其余不会捕获。
staticall(promiseArr){if(!Array.isArray(promiseArr)){consttype=typeofpromiseArr;returnnewTypeError(`TypeError: ${type}${values} is not iterable`)}returnnewMyPromise((resolve,reject)=>{letcounts=0;letresult=[];letlen=promiseArr.length;for(leti=0;i<len;i++){letinstance=promiseArr[i];// Promise.resolve(p)用于处理传入值不为Promise的情况MyPromise.resolve(instance).then(val=>{counts++;result[i]=val;if(counts===len){resolve(result);}},err=>{reject(err);})}})}Promise.race()
只需要一个Promise对象状态变为resolved或rejected时就调用then方法,它只会获取最先执行完成的那个结果。
staticrace(promiseArr){returnnewMyPromise((resolve,reject)=>{for(leti=0;i<promiseArr.length;i++){letinstance=promiseArr[i];Promise.resolve(instance).then(resolve,reject)}})}Promise.allsettled()
allsettled()接受一组 Promise 实例作为参数,但和和.all()方法不同,只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
staticallSettled(promiseArr){if(!Array.isArray(promiseArr)){consttype=typeofpromiseArr;returnnewTypeError(`TypeError: ${type}${values} is not iterable`)}returnnewMyPromise((resolve,reject)=>{letcounts=0;letresult=[];letlen=promiseArr.length;for(leti=0;i<len;i++){letinstance=promiseArr[i];// Promise.resolve(p)用于处理传入值不为Promise的情况MyPromise.resolve(instance).then(res=>{counts++;result.push({status: "fulfilled",value: res});if(counts===len){resolve(result);}},err=>{counts++;result.push({status: "rejected",value: err});if(counts===len){resolve(result);}})}})}Promise.any()
ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
staticany(promiseArr){if(!Array.isArray(promiseArr)){consttype=typeofpromiseArr;returnnewTypeError(`TypeError: ${type}${values} is not iterable`)}returnnewMyPromise((resolve,reject)=>{lethasOneResolved=false;letcounts=0;letlen=promiseArr.length;for(leti=0;i<len;i++){letinstance=promiseArr[i];// Promise.resolve(p)用于处理传入值不为Promise的情况MyPromise.resolve(instance).then(res=>{if(hasOneResolved)return;hasOneResolved=true;resolve(res);},err=>{counts++;if(counts===len){reject('AggregateError: All promises were rejected');}})}})}拓展
Promise是没有中断方法的,fetch是基于Promise的,所以它的请求无法中断,我们来实现一下Promise的中断。
// 包装想要控制中断的Promise, 提供一个abort方法外部调用中断PromisefunctionpromiseAbort(promise){letabort=null;letabortPromise=newPromise((resolve,reject)=>{abort=reject;})letp=Promise.race([promise,abortPromise]);p.abort=abort;returnp;}abort写出来了,日常使用中,有些异步请求需要超时取消,我们可以根据abort,同理自定义超时。
functionpromiseTimeout(promise,time=5000){letabortPromise=newPromise((resolve,reject)=>{setTimeout(()=>{reject('timeout');},time);})returnPromise.race([promise,abortPromise]);}好了,以上就是Promise的基本原理与实现了,希望你能从我的这篇博客中有所收获,谢谢。
代码仓库点击查看
什么是Promise?
Promise 是异步编程的一种解决方案,ES6将其写进了语言标准,原生提供了Promise对象。
Promise主要解决了什么问题?
从零开始手写一个Promise
手写一个Promise需要遵循 Promise/A+规范
首先,我们从Promise最基础的概念开始:
pending,fulfilled,rejected,状态只能从pending到fulfilled或rejected,一旦确定无法改变;value保存成功状态的值,一个reason保存失败状态的值;executor立即执行函数,函数接收resolve和reject两个函数参数. 若是executor函数报错 直接执行reject();resolve()将Promise状态变为fulfilled,reject()将Promise状态变为rejected,并执行then()传入的回调函数;then函数, 依次传入onFulfilled和onRejected回调函数,当status为
fulfilled时执行onFulfilled回调函数,传入this.value。当status为rejected时执行onRejected函数,传入this.reason;按照上面的概念,我们可以写出一个最基本的Promise
写完代码测试一下
但这时候只是同步操作的Promise,若是我们在executor()中传入一个异步操作,则会什么都没有打印出来。
这是因为执行then方法时,此时Promise的状态是pending,不会触发回调。那么在pending状态的时候应该怎么做呢?
不考虑异常的情况下,Promise的状态改变只依赖于构造函数中的resolve()函数和reject函数,那么可以考虑将onFulfilled和onRejected回调函数先保存到Promise中我们定义的属性onFulfilledFn和onRejectedFn中,等到状态改变时再调用。
结合以上思路,优化一下代码:
此时测试一下,会发现浏览器延迟1s打印出‘success 成功’
then的链式调用
这是Promise实现的重点和难点,我们先来看一下JavaScript中原生Promise对象的then是如何链式调用的
这种链式调用该如何实现?
测试一下
这只是最简单的x值判断,只是单纯判断x是否Promise实例,实际上还需要判断更多复杂情况。所以我们可以优化一下,将判断x的函数命名为
resolvePromise提取出来。resolvePromise的参数有promise2(默认返回的promise)、x(我们自己return的对象)、resolve、reject。代码如下:
then的其它细节处理
这样,Promise的主要部分就完成了,完整代码如下:
写完代码测试一下
首先在Promise实现的代码中加入
安装测试脚本
在package.json中配置指令
promises-aplus-tests 中共有 872 条测试用例,代码完美通过

Promise 的 API
原生的Promise还提供了一些方法,我们顺便一起写出来
Promise.resolve()
Promise.reject()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.all()
Promise.all 是解决并发问题的,多个异步并发获取最终的结果(如果有一个失败则失败)
Promise.race()
只需要一个Promise对象状态变为resolved或rejected时就调用then方法,它只会获取最先执行完成的那个结果。
Promise.allsettled()
allsettled()接受一组 Promise 实例作为参数,但和和.all()方法不同,只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
Promise.any()
ES2021 引入了Promise.any()方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
拓展
Promise是没有中断方法的,fetch是基于Promise的,所以它的请求无法中断,我们来实现一下Promise的中断。
abort写出来了,日常使用中,有些异步请求需要超时取消,我们可以根据abort,同理自定义超时。
好了,以上就是Promise的基本原理与实现了,希望你能从我的这篇博客中有所收获,谢谢。
代码仓库点击查看