1.Promise是什么?html
Promise是用来实现JS异步管理的解决方案,经过实例化Promise对象来管理具体的JS异步任务。前端
从Promise管理回调状态的角度来看,Promise又一般被称为状态机,在Promise拥有三种状态:pending、fulfilled、rejected。node
用Promise解决JS异步执行的逻辑来理解,能够说Promise是一个将来的事件,也就是说Promise管理的任务并非在JS的同步线程上当即执行,而是会等待同步线程的程序执行完之后才会执行。ajax
2.建立Promise实例管理一个异步事件,经过then添加(注册)异步任务:编程
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 },(reason) => { 11 console.log("拒绝:" + reason); 12 });
经过示例能够看到Promise实例化对象须要传入一个excutor函数做为参数,这个函数有两个形参resolve、reject分别表示受理事件与拒绝事件。这两个事件也就是经过实例对象调用then方法传入的两个函数,也就是说then方法传入的两个函数分别表示resolve与reject。设计模式
3.微任务与宏任务:数组
在js线程中,XMLHttpRequest网络请求响应事件、浏览器事件、定时器都是宏任务,会被统一放到一个任务队列中(用task queue1表示)。promise
而由Promise产生的异步任务resolve、reject被称为微任务(用task queue2表示)。浏览器
这两种任务的区别就是当异步任务队列中即有宏任务又有微任务时,不管宏任务比微任务早多久添加到任务队列中,都是微任务先执行,宏任务后执行。缓存
来看下面这个示例,了解Promose微任务与宏任务的执行顺序:
1 setTimeout(function(){ 2 console.log(0); //这是个宏任务,被先添加到异步任务队列中 3 },0); 4 let oP = new Promise((resolve, reject) => { 5 resolve(1);//这是个微任务,被后添加到异步任务队列中 6 console.log(2);//这是第一个同步任务,最早被打印到控制台 7 }); 8 oP.then((val) => { 9 console.log(val); 10 },null); 11 console.log(3);//这也是个同步任务,第二个被打印到控制台
测试的打印结果必然是:2 3 1 0;这就是微任务与宏任务的区别,从这一点能够了解到,JS自身实现的Promise是一个全新的功能并不是语法糖,因此除原生promise之外的promise实现都是一种模拟实现,在模拟实现中基本上都是使用setTimeout来实现Promise异步任务的,因此若是不支持原生Promise浏览器使用的是兼容的Promise插件,其Promise异步任务是宏任务,在程序执行时可能会出现与新版本浏览器原生的Promise实现的功能会有些差异,这是须要注意的一个小问题。
4.Promise中的then的链式调用与抛出错误:
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 },(reason) => { 11 console.log("拒绝:" + reason); 12 }).then((val) => { 13 console.log("then2 受理:" + val); 14 },(reason) => { 15 console.log("then2 拒绝:" + reason); 16 });
在上面的示例中,第一个then确定出现两种状况,受理或者拒绝,这个毫无疑问,可是上面的链式调用代码中第二个then注册的resolve与reject永远都只会触发受理,因此最后的执行结果是:
//第一种状况: 受理:ok then2 受理:undefined //第二种状况: 拒绝:no then2 受理:undefined
ES6中的Promise实现的then的链式调用与jQuery的Deferred.then的链式调用是有区别的,jQuery中实现的链式调用的第一个then的受理或者拒绝回调被调用后,后面的then会相应的执行受理或者拒绝。可是ES6中的Promise除第一个then之外后面都是调用受理,这里不过多的讨论jQuery的Deferred的实现,可是这是一个须要注意的问题,毕竟ES6的Promise是总结了前人的经验的基础上设计的新功能,在使用与以前的类似的功能时容易出现惯性思惟。
ES6中的Promise.then链式调用的正确姿式——抛出错误:
这种设计的思考逻辑是:Promise1管理异步任务___>受理 Promise1没有抛出错误:Promise2受理
___>Promise2管理Promise1___>
___>拒绝 Promise2抛出错误:Promise2拒绝
因此前面的示例代码在拒绝中应该添加抛出错误才是正确的姿式:
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 4 },1000); 5 }); 6 oP.then((val) => { 7 console.log("受理:" + val); 8 },(reason) => { 9 console.log("拒绝:" + reason); 10 throw new Error("错误提示..."); 11 }).then((val) => { 12 console.log("then2 受理:" + val); 13 },(reason) => { 14 console.log("then2 拒绝:" + reason); 15 });
在第一个then注册的reject中抛出错误,上面的示例的执行结果就会是这样了:
//第一种状况: 受理:ok then2 受理:undefined //第二种状况: 拒绝:no then2 拒绝:Error: 错误提示...
好像这样的结果并不能说明以前的设计思考逻辑,仅仅只能说明then的链式调用在reject中抛出错误才能触发后面的reject,可是在咱们的开发中必然会有即使异步正确受理,但不表明受理回调就能正确的执行完,受理的代码也可能会出现错误,因此在第一个then中受理回调也抛出错误的话一样会触发后面链式注册的reject,看示例:
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 if(Math.random() * 100 > 30){ 11 return "then1受理成功执行完毕!"; 12 }else{ 13 throw new Error("错误提示:then1受理没有成功执行完成。"); 14 } 15 },(reason) => { 16 console.log("拒绝:" + reason); 17 throw new Error("错误提示..."); 18 }).then((val) => { 19 console.log("then2 受理:" + val); 20 },(reason) => { 21 console.log("then2 拒绝:" + reason); 22 });
这时候整个示例最后的执行结果就会出现三种状况:
//状况1: 受理:ok then2 受理:then1受理成功执行完毕! //状况2: promise.html:24 拒绝:no then2 拒绝:Error: 错误提示... //状况3: 受理:ok then2 拒绝:Error: 错误提示:then1受理没有成功执行完成。
5.then方法链式调用的返回值与传值:
在前面的代码中,相信你已经发现,若是前面then注册的回调不返回值或者不抛出错误,后面的then接收不到任何值,打印出来的参数为undefined。这一点也与jQuery中的then有些区别,在jQuery中若是前面的then没有返回值,后面then注册的回调函数会继续使用前面回调函数接收的参数。
在前面的示例中已经有返回值、抛出错误和传值的展现了,这里重点来看看若是返回值是一个Promise对象,会是什么结果:
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 return new Promise((resolve, reject) => { 11 Math.random() * 100 > 60 ? resolve("then1_resolve_ok") : reject("then_resolve_no"); 12 }); 13 14 },(reason) => { 15 console.log("拒绝:" + reason); 16 return new Promise((resolve, reject) => { 17 Math.random() * 100 > 60 ? resolve("then1_reject_ok") : reject("then1_reject_no"); 18 }) 19 }).then((val) => { 20 console.log("then2 受理:" + val); 21 },(reason) => { 22 console.log("then2 拒绝:" + reason); 23 });
以上的示例出现的结果会有四种:
//状况1、二 受理:ok then2 受理:then1_resolve_ok / then2 拒绝:then1_resolve_no //状况3、四 拒绝:no then2 受理:then1_reject_ok / then2 拒绝:then1_reject_no
经过示例能够看到,当前面一个then的回调返回值是一个Promise对象时,后面的then触发的受理或者拒绝是根据前面返回的Promise对象触发的受理或者拒绝来决定的。
1.在Promise中标准的捕获异常的方法是catch,虽然前面的示例中使用了reject拒绝的方式捕获异常,但通常建议使用catch来实现捕获异常。须要注意的是异常一旦被捕获就不能再次捕获,意思就是若是在链式调用中前面的reject已经捕获了异常,后面链式调用catch就不能再捕获。
建议使用catch异常捕获的代码结构:
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 },(reason) => { 11 console.log("拒绝:" + reason); 12 throw new Error("错误提示..."); 13 }).then((val) => { 14 console.log("then2 受理:" + val); 15 }).catch((err) => { 16 console.log(err); 17 })
catch不能捕获的状况:
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 },(reason) => { 11 console.log("拒绝:" + reason); 12 throw new Error("错误提示..."); 13 }).then((val) => { 14 console.log("then2 受理:" + val); 15 }, (reason) => { 16 console.log("then2 拒绝:" + reason); 17 }).catch((err) => { 18 console.log("异常捕获:",err); 19 }) 20 //这种状况就只能是在第二个then中的reject捕获异常,catch不能捕获到异常
一个非技术性的问题,调用了一个空的then会被忽视,后面的then或者catch依然正常执行:
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 },(reason) => { 11 console.log("拒绝:" + reason); 12 throw new Error("错误提示..."); 13 }) 14 .then()//这个then会被忽视,若是前面一个then调用了reject拒绝,后面的catch能正常捕获(或者后面链式调用then都能正常执行) 15 .catch((err) => { 16 console.log("异常捕获:",err); 17 });
2.在Promise方法中,除了finally都会继续返回Promise对象,并且finally传入的回调函数必定会被执行,这个跟前面的一种状况很是相似,就是当前面的then不抛出错误的时候,后面的then必定是调用受理,实际上底层的实现也就是同一个逻辑上实现的。只是finally再也不返回Promise对象,但须要注意的是finally注册的回调函数获取不到任参数。
1 let oP = new Promise((resolve, reject) => { 2 setTimeout(() => { 3 //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝 4 // (数字大于60表示异步任务成功触发受理,反之失败拒绝) 5 Math.random() * 100 > 60 ? resolve("ok") : reject("no"); 6 },1000); 7 }); 8 oP.then((val) => { 9 console.log("受理:" + val); 10 },(reason) => { 11 console.log("拒绝:" + reason); 12 throw new Error("错误提示..."); 13 }).catch((err) => { 14 console.log("异常捕获:",err); 15 }).finally(() => { 16 console.log("结束");//这个回调函数接收不到任何参数 17 })
3.Promise给并发处理提供了两种实现方式all、race,这两个的处理逻辑很是相似条件运算符的与(&&)或 (||)运算,all就是用来处理当多个Promise所有成功受理就受理自身的受理回调resolve,不然就拒绝reject。race的处理多个Promise只须要一个Promise成功受理就触发自身的受理回调,不然就拒绝reject。它们处理Promise实例的方式都是将Promise实例对象做为数组元素,而后将包裹的数组做为all或race的参数进行处理。
这里使用一段nodejs环境读取文件代码来展现Promise.all的使用:
//路径+文件名: 内容:data ./data/number.txt "./data/name.txt" ./data/name.txt "./data/score.tet" ./data/score/txt "99" //src目录结构 --index.js --data ----number.txt ----name.txt ----score.txt
Promise.all实现文件数据并发读取:
1 let fs = require('fs'); 2 3 function readFile(path){ 4 return new Promise((resolve,reject) => { 5 fs.readFile(path,'utf-8', (err,data) => { 6 if(data){ 7 resolve(data); 8 }else{ 9 reject(err); 10 } 11 }); 12 }); 13 } 14 15 Promise.all([readFile("./data/number.txt"),readFile("./data/name.txt"),readFile("./data/score.txt")]).then((val) =>{ 16 console.log(val); 17 });
在nodejs环境中执行代码,打印结果:
node index.js //执行js文件 [ './data/name.txt', './data/score.txt', '99' ] //打印结果
从示例中能够看到Promise.all获取的值是所有Promise实例受理回调传入的值,而且以数组的方式传入。
接着来看一个Promise.race的示例,这个示例:
1 var op1 = new Promise((resolve, reject) => { 2 setTimeout(resolve, 500, "one"); 3 }); 4 var op2 = new Promise((resolve, reject) => { 5 setTimeout(resolve, 100, "two"); 6 }); 7 Promise.race([op1,op2]).then((val) => { 8 console.log(val); 9 }); 10 //打印结果:two
Promise.race获的值是第一个Promise实例受理回调传入的值。
4.Promise.all与Promise.race的传值规则:
all:
全部Promise实例受理resolve,即全部异步回调成功的状况下,将全部Promise实例的resolve接收的参数合并成一个数组,传递给Promise.all生成的新的Promise实例的resolve回调处理。
若是有一个失败的状况下,即Promise.all生成的新的Promise实例触发回调reject函数,这个函数会接收到最早失败的Promise实例经过reject回调传入的参数。
race:
经过Promise.race处理的Promise实例中最早得到结果的Promise实例的参数,传递给Promise.race产生的Promise实例,不论成功与失败,成功就出发resolve函数,失败就出发reject函数。
1.链式调用解决回调地狱:在一开始学习编程的时候咱们必定都写过一连串的做用域嵌套代码,来解决一些业务逻辑链相对比较长的功能,而后还可能跟同窗炫耀“你看我把这个功能写出来了,还能正确执行”。不要问我为何这么确定,这种事我作过,个人同窗和朋友也有作过。这为何值得炫耀呢?无非就是面对这种业务逻辑链比较长的功能很难保证在那个不环节不出错,因此能驾驭层层嵌套的代码的确能够说很“认真”的在编码。我在jQuery的ajax的一篇博客中就是用了一个很是详细的案例展现了回调地狱:jQuery使用(十二):工具方法之ajax的无忧回调(优雅的代码风格)
这里我使用第二节中的(3:Promise.all)案例,(应用以前的文件结构)来写一个文件层级读取的示例:
1 //这是一个基于nodejs环境的js示例,请在nodejs环境中执行index.js 2 let fs = require('fs'); 3 4 fs.readFile("./data/number.txt","utf-8",(err,data) => { 5 if(data){ 6 fs.readFile(data,"utf-8",(err,data) => { 7 if(data){ 8 fs.readFile(data,"utf-8",(err,data) => { 9 console.log(data); 10 }) 11 } 12 }) 13 } 14 });
相信你们遇到这种代码都会知道这样的代码结构,不易于维护,编写容易出错而且还不容易追踪错误。下面来看看使用Promise如何回避这样的问题,来提升代码质量:
1 //这是一个基于nodejs环境的js示例,请在nodejs环境中执行index.js 2 let fs = require('fs'); 3 4 function readFile(path){ 5 return new Promise((resolve,reject) => { 6 fs.readFile(path,'utf-8', (err,data) => { 7 if(data){ 8 resolve(data); 9 }else{ 10 reject(err); 11 } 12 }); 13 }); 14 } 15 readFile("./data/number.txt").then((val) => { 16 return readFile(val);//这里去获取nama.text的文本数据 17 },(reason) => { 18 console.log(reason); 19 }).then((val) => { 20 return readFile(val);//这里去获取score.text的文本数据 21 },(reason) => { 22 console.log(reason); 23 }).then((val) => { 24 console.log(val);//这里最后打印score.text的文本数据 25 },(reason) => { 26 console.log(reason); 27 });
2.异步回调如今与将来任务分离:
Kyle Simpson大神在《你不知道的js中卷》的第二部分第一章(1.3并行线程)中给我说明了一个我长期混洗的知识点,“异步”与“并行”,他明确的阐述了异步是关于如今和未来的事件间隙,而并不是关于能同时发生的事情。
简单来讲,在js中咱们能够把同步任务理解为如今要执行的任务,异步则是未来要执行的任务,我的认为这是Promise的核心功能,Promise的then本质上就是这样的设计思路,在实例化的Promise对象的时候就已经调用了回调任务resolve或者reject,可是Promise将这两个回调任务处理成了异步(微任务)模式,经过前面的应用介绍咱们知道Promise实例化的时候并无添加这两个任务,而是后面基于同步任务的then添加的,因此resolve和reject才能在将来有真正的任务能够执行。
利用异步的这种如今与将来的异步设计思路实现了Promise.all和Promise.race,解决了前端回调的竞态问题。关于js竞态问题能够了解《你不知道的js中卷》第二部分第一章和第三章的3.1。(这给内容可多可少,可是想一想Kyle Simpson的清晰明了的分析思路,建议你们去看他书。)
3.信任问题(控制反转):
相信你们在应用js开发的时候都使用果相似这样的代码:
ajax("...",function(...){ ... })
一般这样的代码咱们都会想到插件或者第三方库,若是这是一个购物订单,你知道这段代码存在多大的风险吗?咱们根本就不知道这个回调函数会被执行多少次,由于怎么执行是由别让人的插件和库来控制的。顺着这个思路,在《你不知道的js中卷》的第二部分第二章2.3.1最后,大神提出这样的追问:调用过早怎么办?调用过晚怎么办?调用屡次或者次数太少怎么办?没有传递参数或者环境怎么办?出现错误或者异常怎么办?这些内容在《你不知道的js中卷》第二部分第三章3.3都详细的描述了基于Promise的解决方案。
本质上也就是Promise的控制反转的设计模式,好比前面的ajax()请求能够这样来写:
var oP = new Promise((resolve,reject) => { resolve(...); }); oP.then((val) => { ajax("...",function(...){...}); });
咱们知道,每一个Promise只能决议一次,不管成功或者失败,因此就不用小心一个购物订单请求会不会被插件或者第三方库误操做发送屡次(这并非绝对的,毕竟ajax回调函数内部怎么执行仍是别人的代码,这里我能只能假设ajax回调函数是可信任的)。
关于Promise的实现目的还有不少,我也只能在这里列举一些比较典型的和常见的问题,若是想了解更多我首先建议你们去看我前面屡次提到的书,或者到各大技术论坛了解他人的研究和发现,下面接着进入激动人心的Promise源码部分。
Promise实现标准文档:https://promisesaplus.com
因为源码的复杂性还算比较高,咱们采用分阶段实现的方式,从Promise的一部分功能开始而后逐渐完成全部功能。
第一阶段:基于Promise的三种状态:pending、Fulfilled、Rejected实现同步的状态决议回调任务处理;
第二阶段:基于阶段一的状态机实现Promise异步的状态决议回调任务处理;
第三阶段:实现then的链式调用;
第四阶段:使用setTimeout模拟实现Promise异步回调任务、处理回调任务中的异常、忽略链式调用中的空then;
第五阶段:实现回调函数返回Promise实例;
第六阶段:实现Promise静态方法race、all;
第七阶段:实现Promise原型方法catch、finally、以及扩展一个deferred静态方法
1.原理分析之状态:
Promise实例三种状态:pending、Fulfilled、Rejected,当pending状态时表示未决议,可转换成Fulfilled或者Rejected状态,转换状态后不可更改。
Promise实例化时执行excutor函数,并使用try...catch处理excutor可能抛出的错误行为,若是抛出错误,将状态设置为Rejected(拒绝)。
在原型上定义then方法,实现回调任务处理。
1 function myPromise(excutor){ 2 var self = this; 3 self.status = "pending"; 4 self.resolveValue = null; //缓存受理回调的参数 5 self.rejectReason = null; //缓存拒绝回调的参数 6 function resolve(value){ 7 if(self.status === "pending"){ 8 self.status = "Fulfilled"; 9 self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上 10 } 11 } 12 13 function reject(reason){ 14 if(self.status === "pending"){ 15 self.status = "Rejected"; 16 self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上 17 } 18 } 19 //当excutor抛出错误执行reject 20 try{ 21 excutor(resolve,reject); 22 }catch(e){ 23 reject(e); 24 } 25 26 }; 27 28 myPromise.prototype.then = function(onFulfilled,onRejected){ 29 var self = this; 30 if(self.status === "Fulfilled"){ 31 onFulfilled(self.resolveValue); 32 } 33 if(self.status === "Rejected"){ 34 onRejected(self.rejectReason); 35 } 36 }
测试代码:
1 var myP = new myPromise((resolve,reject) => { 2 // 测试resolve 3 // resolve("受理"); 4 // 测试reject 5 // reject("拒绝"); 6 // 测试抛出错误 7 throw new Error("excutor抛出错误"); 8 }); 9 myP.then((val) => { 10 console.log(val); 11 }, (reason) => { 12 console.log(reason); 13 });
2.Promise原理分析之异步:
这部分还不是解析Promise微任务的内容,而是解析当excutor内决议是一个异步任务,好比ajax请求的回调任务,这种状况就是then的注册行为会在状态变化以前,因此须要将注册回调函数缓存下来,等到异步任务执行时调用。
1 function myPromise(excutor){ 2 var self = this; 3 self.status = "pending"; 4 self.resolveValue = null; //缓存受理回调的参数 5 self.rejectReason = null; //缓存拒绝回调的参数 6 self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数 7 self.RejectCallBackList = []; //当Promise是一个异步任务时,缓存拒绝回调函数 8 9 function resolve(value){ 10 if(self.status === "pending"){ 11 self.status = "Fulfilled"; 12 self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上 13 self.ResolveCallBackList.forEach(function(ele){ 14 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数 15 ele(); 16 }); 17 } 18 } 19 20 function reject(reason){ 21 if(self.status === "pending"){ 22 self.status = "Rejected"; 23 self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上 24 self.RejectCallBackList.forEach(function(ele){ 25 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数 26 ele(); 27 }); 28 } 29 } 30 //当excutor抛出错误执行reject 31 try{ 32 excutor(resolve,reject); 33 }catch(e){ 34 reject(e); 35 } 36 37 }; 38 39 myPromise.prototype.then = function(onFulfilled,onRejected){ 40 var self = this; 41 if(self.status === "Fulfilled"){ 42 onFulfilled(self.resolveValue); 43 } 44 if(self.status === "Rejected"){ 45 onRejected(self.rejectReason); 46 } 47 // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变 48 // 因此异步做为一个未来任务,先缓存到Promise实例对象上 49 if(self.status === "pending"){ 50 self.ResolveCallBackList.push(function(){ 51 onFulfilled(self.resolveValue); 52 }); 53 54 self.RejectCallBackList.push(function(){ 55 onRejected(self.rejectReason); 56 }) 57 } 58 }
测试代码:(异步任务的出现异常报错这部分还没处理,因此只测试异步任务的受理或拒绝)
1 var myP = new myPromise((resolve,reject) => { 2 setTimeout(() => { 3 // 测试resolve 4 resolve("受理"); 5 // 测试reject 6 // reject("拒绝"); 7 },1000); 8 }); 9 myP.then((val) => { 10 console.log(val); 11 }, (reason) => { 12 console.log(reason); 13 });
3.then的链式调用:
在ES6的Promise中,then的链式调用是返回一个全新的Promise实例,这一点在前面的应用中已经有说明,链式调用中除了返回一个全新的Promise对象之外,还有一个关键的问题就是将前面的Promise的的返回值,做为参数传给后面一个Promise实例的回调函数使用。
这个阶段暂时不处理返回Promise实例的相关内容,因此还记得我在使用的第一节第四小点,这里测试第一个Promise实例的resolve和reject第二个then注册受理和拒绝只会触发受理。
因此这样做为一个基本链式调用实现就很是的简单了,由于Promise实例化时须要执行一个同步的回调函数excutor,咱们都知道,then的回调注册时同步进行,因此咱们只须要将then的注册放到须要心生成的Promise实例化时同步执行excutor中,而后获取前一个Promise的回调执行返回值,做为新生成的Promise实例回调的参数传入便可,这个提及来好像有点复杂,可是实现很是的简单,建议直接看代码:
1 function myPromise(excutor){ 2 var self = this; 3 self.status = "pending"; 4 self.resolveValue = null; //缓存受理回调的参数 5 self.rejectReason = null; //缓存拒绝回调的参数 6 self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数 7 self.RejectCallBackList = []; //当Promise是一个异步任务时,缓存拒绝回调函数 8 9 function resolve(value){ 10 if(self.status === "pending"){ 11 self.status = "Fulfilled"; 12 self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上 13 self.ResolveCallBackList.forEach(function(ele){ 14 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数 15 ele(); 16 }); 17 } 18 } 19 20 function reject(reason){ 21 if(self.status === "pending"){ 22 self.status = "Rejected"; 23 self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上 24 self.RejectCallBackList.forEach(function(ele){ 25 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数 26 ele(); 27 }); 28 } 29 } 30 //当excutor抛出错误执行reject 31 try{ 32 excutor(resolve,reject); 33 }catch(e){ 34 reject(e); 35 } 36 37 }; 38 39 myPromise.prototype.then = function(onFulfilled,onRejected){ 40 var self = this; 41 42 var nextPromise = new myPromise(function (resolve,reject) { 43 44 if(self.status === "Fulfilled"){ 45 var nextResolveValue = onFulfilled(self.resolveValue); 46 resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 47 } 48 if(self.status === "Rejected"){ 49 var nextRejectValue = onRejected(self.rejectReason); 50 resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 51 } 52 // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变 53 // 因此异步做为一个未来任务,先缓存到Promise实例对象上 54 if(self.status === "pending"){ 55 self.ResolveCallBackList.push(function(){ 56 var nextResolveValue = onFulfilled(self.resolveValue); 57 resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 58 }); 59 60 self.RejectCallBackList.push(function(){ 61 var nextRejectValue = onRejected(self.rejectReason); 62 resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 63 }); 64 } 65 }); 66 return nextPromise; 67 }
测试代码:
1 var myP = new myPromise((resolve,reject) => { 2 setTimeout(() => { 3 // 测试resolve 4 // resolve(0); 5 // 测试reject 6 reject(0); 7 },1000); 8 }); 9 myP.then((val) => { 10 console.log("受理:" + val); 11 return 1; 12 }, (reason) => { 13 console.log("拒绝:" + reason); 14 return "1"; 15 }).then((val) => { 16 console.log("受理:" + val); 17 }, (reason) => { 18 console.log("拒绝:" + reason);//这个暂时不会执行到 19 });
4.使用setTimeout模拟实现Promise异步回调任务:
注意这种模拟实现与ES6实现的异步回调有一个根本性差别,ES6的Promise异步回调任务是微任务,可是经过setTimeout模拟实现的是宏任务。实现其实也是很是的简单,只须要将回调任务放到setTimeout的回调函数中便可,并设置延迟时间为0;
而后再在这部分实现对回调任务中抛出错误的处理,这是由于回调任务中的错误须要在下一个Promise的reject中或者catch中被捕获,因此有了链式调用的基础就能够来实现这个功能了。
还有第二节第一小点钟提到忽略链式调用中的空then(),关于这个问题我前面只在使用中说会忽略这个空then,可是实际底层的实现并不是时忽略,而是将前一个Promise基于这个空的Promise实例传递给了下一个非空的Promise。这里咱们先来看一段基于原生的Promise手动传递应用:
var myP = new Promise((resolve,reject) => { setTimeout(() => { // 测试resolve // resolve(0); // 测试reject reject(0); },1000); }); myP.then((val) => { console.log("受理:" + val); return 1; }, (reason) => { console.log("拒绝:" + reason); // 测试Error throw new error("这里抛出错误"); }).then((val) => { return val; //将前一个受理的返回值传递给下一个Promise受理回调 },(reason) => { throw new Errror(reason); //将前一个Promise抛出的错误传递给下一个Promise的拒绝回调 }) .then((val) => { console.log("受理:" + val); }, (reason) => { console.log("拒绝:" + reason);//这个暂时不会执行到 });
实际上,Promise底层也是基于这样的传递行为来处理空then的,并且在前面的Promise应用介绍中,有一种状况没有深刻的说明,就是当then(null,(...) => {...})、then((...) => {...},null)、then(null,null)进行深刻的说明,请示本质上一样是使用了上面示例中的传递行为。仍是那句话,提及来很是复杂,实际代码很是简单:
1 function myPromise(excutor){ 2 var self = this; 3 self.status = "pending"; 4 self.resolveValue = null; //缓存受理回调的参数 5 self.rejectReason = null; //缓存拒绝回调的参数 6 self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数 7 self.RejectCallBackList = []; //当Promise是一个异步任务时,缓存拒绝回调函数 8 9 function resolve(value){ 10 if(self.status === "pending"){ 11 self.status = "Fulfilled"; 12 self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上 13 self.ResolveCallBackList.forEach(function(ele){ 14 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数 15 ele(); 16 }); 17 } 18 } 19 20 function reject(reason){ 21 if(self.status === "pending"){ 22 self.status = "Rejected"; 23 self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上 24 self.RejectCallBackList.forEach(function(ele){ 25 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数 26 ele(); 27 }); 28 } 29 } 30 //当excutor抛出错误执行reject 31 try{ 32 excutor(resolve,reject); 33 }catch(e){ 34 reject(e); 35 } 36 37 }; 38 39 myPromise.prototype.then = function(onFulfilled,onRejected){ 40 if(!onFulfilled){ //当没有传入受理回调函数时,自动将参数传递给下一个Promise实例的受理函数做为参数 41 onFulfilled = function(val){ 42 return val; 43 } 44 } 45 if(!onRejected){ //当没有传入拒绝回调函数时,自动将参数传递给下一个Promise实例的拒绝函数做为参数 46 // 可能这里你会疑惑,为何要使用抛出错误的方式传递 47 // 前面已经说明过,拒绝回调只有在Promise实例化中调用了拒绝回调函数之外,只有抛出错误才会会触发下一个Promise实例的拒绝回调 48 onRejected = function(reason){ 49 throw new Error(reason); 50 } 51 } 52 var self = this; 53 54 var nextPromise = new myPromise(function (resolve,reject) { 55 56 if(self.status === "Fulfilled"){ 57 setTimeout(function(){ //使用setTimeout模拟实现异步回调 58 try{ //使用try...catch来捕获回调任务的异常 59 var nextResolveValue = onFulfilled(self.resolveValue); 60 resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 61 }catch(e){ 62 reject(e); 63 } 64 65 },0); 66 } 67 if(self.status === "Rejected"){ 68 setTimeout(function(){ 69 try{ 70 var nextRejectValue = onRejected(self.rejectReason); 71 resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 72 }catch(e){ 73 reject(e); 74 } 75 76 },0); 77 } 78 // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变 79 // 因此异步做为一个未来任务,先缓存到Promise实例对象上 80 if(self.status === "pending"){ 81 self.ResolveCallBackList.push(function(){ 82 setTimeout(function(){ 83 try{ 84 var nextResolveValue = onFulfilled(self.resolveValue); 85 resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 86 }catch(e){ 87 reject(e); 88 } 89 90 },0); 91 }); 92 93 self.RejectCallBackList.push(function(){ 94 setTimeout(function(){ 95 try{ 96 var nextRejectValue = onRejected(self.rejectReason); 97 resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务 98 }catch(e){ 99 reject(e); 100 } 101 102 },0); 103 }); 104 } 105 }); 106 return nextPromise; 107 }
这部分功能已经很是接近原生Promise了,就不提供测试代码了,下面直接进入第五阶段。
5.实现回调函数返回Promise实例:
关于回调返回Promise实例与前面的空then的处理思路很是相识,在空then的状况下咱们须要将值传递给下一个then生成的Promise实例。那回调返回Promise实例就是须要,将本来注册给then自身生成的Promise实例的回调从新注册给上一个Promise回调返回的Promise实例,实现代码一样很是简单:
1 function myPromise(excutor){ 2 var self = this; 3 self.status = "pending"; 4 self.resolveValue = null; //缓存受理回调的参数 5 self.rejectReason = null; //缓存拒绝回调的参数 6 self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数 7 self.RejectCallBackList = []; //当Promise是一个异步任务时,缓存拒绝回调函数 8 9 function resolve(value){ 10 if(self.status === "pending"){ 11 self.status = "Fulfilled"; 12 self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上 13 self.ResolveCallBackList.forEach(function(ele){ 14 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数 15 ele(); 16 }); 17 } 18 } 19 20 function reject(reason){ 21 if(self.status === "pending"){ 22 self.status = "Rejected"; 23 self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上 24 self.RejectCallBackList.forEach(function(ele){ 25 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数 26 ele(); 27 }); 28 } 29 } 30 //当excutor抛出错误执行reject 31 try{ 32 excutor(resolve,reject); 33 }catch(e){ 34 reject(e); 35 } 36 37 }; 38 39 //用来处理回调返回值的状况:当返回值为Promise时,将回调函数注册到该Promise上,若是为普通值直接执行回调函数 40 function ResolutionRetrunPromise(returnValue,res,rej){ 41 if(returnValue instanceof myPromise){ 42 returnValue.then(function(val){ 43 res(val); 44 },function(reason){ 45 rej(reason); 46 }); 47 }else{ 48 res(returnValue); 49 } 50 } 51 52 53 myPromise.prototype.then = function(onFulfilled,onRejected){ 54 if(!onFulfilled){ //当没有传入受理回调函数时,自动将参数传递给下一个Promise实例的受理函数做为参数 55 onFulfilled = function(val){ 56 return val; 57 } 58 } 59 if(!onRejected){ //当没有传入拒绝回调函数时,自动将参数传递给下一个Promise实例的拒绝函数做为参数 60 // 可能这里你会疑惑,为何要使用抛出错误的方式传递 61 // 前面已经说明过,拒绝回调只有在Promise实例化中调用了拒绝回调函数之外,只有抛出错误才会会触发下一个Promise实例的拒绝回调 62 onRejected = function(reason){ 63 throw new Error(reason); 64 } 65 } 66 var self = this; 67 68 var nextPromise = new myPromise(function (resolve,reject) { 69 70 if(self.status === "Fulfilled"){ 71 setTimeout(function(){ //使用setTimeout模拟实现异步回调 72 try{ //使用try...catch来捕获回调任务的异常 73 var nextResolveValue = onFulfilled(self.resolveValue); 74 ResolutionRetrunPromise(nextResolveValue,resolve,reject);//使用回调返回值来处理下一个回调任务 75 }catch(e){ 76 reject(e); 77 } 78 79 },0); 80 } 81 if(self.status === "Rejected"){ 82 setTimeout(function(){ 83 try{ 84 var nextRejectValue = onRejected(self.rejectReason); 85 ResolutionRetrunPromise(nextRejectValue,resolve,reject); 86 }catch(e){ 87 reject(e); 88 } 89 90 },0); 91 } 92 // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变 93 // 因此异步做为一个未来任务,先缓存到Promise实例对象上 94 if(self.status === "pending"){ 95 self.ResolveCallBackList.push(function(){ 96 setTimeout(function(){ 97 try{ 98 var nextResolveValue = onFulfilled(self.resolveValue); 99 ResolutionRetrunPromise(nextResolveValue,resolve,reject); 100 }catch(e){ 101 reject(e); 102 } 103 104 },0); 105 }); 106 107 self.RejectCallBackList.push(function(){ 108 setTimeout(function(){ 109 try{ 110 var nextRejectValue = onRejected(self.rejectReason); 111 ResolutionRetrunPromise(nextRejectValue,resolve,reject); 112 }catch(e){ 113 reject(e); 114 } 115 116 },0); 117 }); 118 } 119 }); 120 return nextPromise; 121 }
测试代码:
1 var mP = new myPromise((resolve,reject) => { 2 // resolve("受理1"); 3 reject("拒绝1"); 4 }); 5 mP.then((val) => { 6 console.log(val); 7 return new myPromise((resolve,reject) => { 8 // resolve("受理2"); 9 // reject("拒绝2"); 10 }); 11 },(reason) => { 12 console.log(reason); 13 return new myPromise((resolve,reject) => { 14 // resolve("受理2"); 15 reject("拒绝2"); 16 }); 17 }).then((val) => { 18 console.log(val); 19 },(reason) => { 20 console.log(reason); 21 })
6.实现Promise静态方法race、all:
1 myPromise.race = function(promiseArr){ 2 return new myPromise(function(resolve,reject){ 3 promiseArr.forEach(function(promise,index){ 4 promise.then(resolve,reject); 5 }); 6 }); 7 } 8 9 //all经过给每一个Promise传递一个受理回调,这个回调负责获取每一个受理函数的参数,并判断是否所有受理,若是所有受理触发all自身的受理回调 10 //另外将all的reject传递给每一个Promise的reject,只要任意一个触发就完成all的拒绝回调 11 myPromise.all = function(promiseArr){ 12 function gen(length,resolve){ 13 var count = 0; 14 var values = []; 15 return function(i,value){ 16 values[i] = value; 17 if(++count === length){ 18 resolve(values); 19 } 20 } 21 } 22 return new myPromise(function(resolve,reject){ 23 let done = gen(promiseArr.length,resolve); 24 promiseArr.forEach(function(promise,index){ 25 promise.then((val) => { 26 done(index,val); 27 },reject); 28 }) 29 }) 30 }
7.实现Promise原型方法catch、finally、以及扩展一个deferred静态方法
1 //原型方法catch的实现 2 myPromise.prototype.catch = function(onRejected){ 3 return this.then(null,onRejected); 4 } 5 // 原型方法finally 6 myPromise.prototype.finally = function(fun){ 7 fun(); 8 } 9 //扩展静态方法deferred方法 10 myPromise.deferred = function(){ 11 var defer = {}; 12 defer.promise = new Promise((resolve,reject) => { 13 defer.resolve = resolve; 14 defer.reject = reject; 15 }); 16 return defer; 17 }
(全文完)