一、Promise的内部实现原理
-
Promise
1)Promise 是异步编程的一种解决方案,比传统的异步解决方案回调函数和事件更合理、更强大,现已被 ES6 纳入进规范中,Javascript 异步操作的基本单位也逐渐从 callback 转换到 promise
2)Promise 表示为一个异步操作的最终结果,ECMAscript 规范定义为延时或异步计算最终结果的占位符
3)Promise会做出正确延时或异步操作,会解决callback处理异步回调可能产生的调用过早,调用过晚、调用次数过多过少、吞掉可能出现的错误或异常问题等,只接受首次 resolve(…)或 reject(…) 决议,本身状态转变后不会再变,所有通过 then(…) 注册的回调总是依次异步调用,所有异常总会被捕获抛出
4)严谨来讲,Promise 是一种封装和组合未来值得易于复用机制,实现关注点分离、异步流程控制、异常冒泡、串行/并行控制等 -
thenable 对象
1)thenable 是一个定义 then(…) 方法的对象或函数
2)thenable 对象的存在目的是使 Promise 的实现更具有通用性,只要其暴露出一个遵循 Promise/A+ 规范的 then(…) 方法,同时也会使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存
3)类型检查:也可以称之为鸭式辩型(duck typing),识别 thenable 或行为类似 Promise 对象可以根据其是否具有 then(…) 方法来判断 -
then 回调异步执行
1)Promise 实例化时传入的函数会立即执行,then(…) 中的回调需要异步延迟调用
2)回调函数异步调用时机:可以理解为onFulfilled 或 onRejected 只在执行环境堆栈仅包含平台代码时才可被调用
3)实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行,这个事件队列可以采用宏任务 macro-task机制或微任务 micro-task机制来实现
4)在ES6中,Promise 必须以 Promise Job 形式加入 job queues,也就是微任务micro-task。Job Queue 是 ES6 中新提出的概念,建立在事件循环队列之上,job queue存在也是为了满足一些低延迟的异步操作
5) macrotask和 microtask 分别表示异步任务的两种分类,在挂起任务时,JS 引擎会将所有任务按照类别分到两个队列中,首先在 macrotask 的队列(也叫 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的所有任务顺序执行;之后再取 macrotask 任务,周而复始,直至两个队列的任务都取完 -
Promise 状态
1)Promise 的三种状态:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise被resolve或reject,不能再迁移至其他任何状态(即状态 immutable)
2)fulfilled 使用 resolved 代替,onFulfilled 使用 onResolved 代替 -
Promise 构造函数
1)Promise构造函数需要完成
初始化 Promise 状态(pending)
初始化 then(…) 注册回调处理数组(then 方法可被同一个 promise 调用多次)
立即执行传入的 fn 函数,传入Promise 内部 resolve、reject 函数
2)then 方法可以被同一个 promise 调用多次
3)如果需要多次调用 then 注册回调处理,内部选择使用 _deferreds 数组存储处理对象
6.then 函数
1)then 方法可以被同一个 promise 调用多次,每次返回新 promise 对象
2)then 方法接受两个参数onResolved、onRejected(可选),在 promise 被 resolve 或 reject 后,所有 onResolved 或 onRejected 函数须按照其注册顺序依次回调,且调用次数不超过一次
3)then 函数执行流程
实例化空 promise 对象用来返回(保持then链式调用)
构造 then(…) 注册回调处理函数结构体
判断当前 promise 状态,pending 状态存储延迟处理对象 deferred ,非pending状态执行 onResolved 或 onRejected 回调
4)Handler 函数封装存储 onResolved、onRejected 函数和新生成 promise 对象
-
链式调用返回新的promise
1)为保证 then 函数链式调用,then 需要返回 promise 实例
2) Promise 状态一旦 resolved 或 rejected就不能再迁移
3)handleResolved 函数功能为根据当前 promise 状态,异步执行 onResolved 或 onRejected 回调函数,因在 resolve 或 reject 函数内部同样需要相关功能,提取为单独模块 -
resolve 函数
1)Promise 实例化时立即执行传入的 fn 函数,同时传递内部 resolve 函数作为参数用来改变 promise 状态
2)resolve 函数:判断并改变当前 promise 状态,存储 resolve(…) 的 value 值,判断当前是否存在 then(…) 注册回调执行函数,若存在则依次异步执行 onResolved 回调
3)为使 Promise 的实现更具有通用性,当 value 为存在 then(…) 方法的 thenable 对象,需要做Promise Resolution Procedure
处理,规范描述为[[Resolve]](promise, x)
,x 即 为后面 value 参数
4)处理逻辑流程
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
如果 x 为 Promise ,则使 promise 接受 x 的状态
如果 x 为对象或函数
把 x.then 赋值给 then
如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
如果 then 是函数,将 x 作为函数的作用域 this 调用之
如果 x 不为对象或者函数,以 x 为参数执行 promise
5)resolve 函数逻辑较为复杂,主要集中在处理 value(x)值多种可能性
6)如果 value 为 Promise 且状态为pending时,须使 promise 接受 value 的状态
在 value 状态为 pending 时,简单将 promise 的 deferreds 回调处理数组赋予 value deferreds变量
非 pending 状态,使用 value 内部值回调 promise 注册的 deferreds
如果 value 为 thenable 对象,以 value 作为函数的作用域 this 调用之,同时回调调用内部 resolve(…)、reject(…)函数
其他情形则以 value 为参数执行 promise,调用 onResolved 或 onRejected 处理函数
7)Promise Resolution Procedure
处理流程是用来处理 then(…) 注册的 onResolved 或 onRejected 调用返回值 与 then 新生成 promise 之间关系,不过考虑到 fn 函数内部调用 resolve(…)产生值 与当前 promise 值仍然存在相同关系,逻辑一致,写进相同模块 -
reject 函数
1)Promise 内部私有方法 reject 相较于 resolve 逻辑简单很多
2)resolve函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
reject函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去 -
handleResolved 函数
1)handleResolved 函数具体会根据 promise 当前状态判断调用 onResolved、onRejected,处理 then(…) 注册回调为空情形,以及维护链式 then(…) 函数后续调用
2)使用deferred.promise
作为当前 promise 结合 value 调用后续处理函数继续往后执行,实现值穿透空处理函数往后传递
3)实现 then 函数的链式调用,只需要在Promise.prototype.then(..)
处理函数中返回新的 promise 实例即可,还需要依次调用 then 注册的回调处理函数 -
then 注册回调函数异步执行
1)为屏蔽依赖外部的不确定性,规范指定 onFulfilled 和 onRejected 方法异步执行
12.promise 内部错误或异常
1)如果 promise 被 rejected,则会调用拒绝回调并传入拒由,比如在 Promise 的创建过程中(fn执行时)出现异常,那这个异常会被捕捉并调用 onRejected
2)如果 Promise 完成后调用 onResolved 查看结果时出现异常错误,此时 onRejected 不会被触发执行,因为 onResolved 内部异常并不会改变当前 promise 状态(仍为resolved),而是改变 then 中返回新的 promise 状态为 rejected,异常未丢失但也未调用错误处理函数
3)Promise.prototype.catch方法,假如你对 onResolved 处理过程没有信心或存在异常 case 情况,最好还是在 then 函数后调用 catch 方法做异常捕获兜底处理
二、Promise的常见问题
-
创建promise的流程
1)使用new Promise(fn)
或者它的快捷方式Promise.resolve()
、Promise.reject()
,返回一个promise对象
2)在fn中指定异步的处理
处理结果正常,调用resolve
处理结果错误,调用reject -
reject 和 catch 的区别
1)promise.then(onFulfilled, onRejected)
在onFulfilled中发生异常的话,在onRejected中是捕获不到这个异常的
2)promise.then(onFulfilled).catch(onRejected) .then
中产生的异常能在.catch中捕获
3)建议使用第二种,因为能捕获之前的所有异常
4)第二种的.catch()也可以使用.then()表示,它们本质上是没有区别的,.catch === .then(null, onRejected)
-
如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误
-
在异步回调中抛错,不会被catch到
-
promise状态变为resove或reject,就凝固了,不会再改变
三、Promise的综合理解
- Promise的实例实现
1)需求分析:
Promise可以理解为保姆,所有的事情都可以吩咐它去做,你就可以做其它的事情了
场景: 比如一天我需要加班,老婆也要加班,需要去送饭,所以我让保姆去做这个事情
保姆需要完成的任务是:
先去超市买菜,买菜的任务
然后用超市买回来的菜做饭,做饭的任务
将做好的菜送到老婆的单位,送饭的任务
送完饭后打电话和我通知说一下,打电话通知的任务
第一、二、三个任务为异步任务,第四个任务为同步任务
2)实例代码:
// 买菜的任务
function buyVegetables(){
var p1 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve(["鸡蛋","西红柿","辣椒"]);
},3000);
});
return p1;
}
// 做饭的任务
function cookDinner(){
var p2 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve({
food:"米饭",
greens:"西红柿炒蛋"
});
},3000);
});
return p2;
}
// 送饭的任务
function mealDelivery(){
var p3 = new Promise(function(resolve,reject){
setTimeout(function(){
resolve("饭做好了,开始去送饭了");
},3000);
});
return p3;
}
// 打电话通知的任务
function inform(){
var p4 = new Promise(function(resolve,reject){
console.log("饭已经送到了,可以吃饭了");
});
return p4;
}
// 告诉保姆任务的执行
/*
* 输出:
* (3) ["鸡蛋", "西红柿", "辣椒"]
* {food: "米饭", greens: "西红柿炒蛋"}
* 饭做好了,开始去送饭了
* 饭已经送到了,可以吃饭了
*/
// 第一个、第二个和第三个是异步任务,每隔三秒后才出现,第四个是同步任务,与第三个一起出现
// 一个 Promise 对象有三个状态,并且状态一旦改变,便不能再被更改为其他状态
// pending,异步任务正在进行 resolved (也可以叫fulfilled),异步任务执行成功 rejected,异步任务执行失败
buyVegetables().then(function(data){
console.log(data);
return cookDinner();
}).then(function(data){
console.log(data);
return mealDelivery();
}).then(function(data){
console.log(data);
return inform();
});
-
Promise的总结
1)首先初始化一个 Promise 对象,可以通过两种方式创建, 这两种方式都会返回一个 Promise 对象
new Promise(fn)
和Promise.resolve(fn)
2)然后调用上一步返回的 promise 对象的 then 方法,注册回调函数,then 中的回调函数可以有一个参数,也可以不带参数,如果 then 中的回调函数依赖上一步的返回结果,那么要带上参数
3)最后注册 catch 异常处理函数,处理前面回调中可能抛出的异常 -
Promise的事件循环
1)Promise在初始化时,传入的函数是同步执行的,然后注册 then 回调。注册完之后,继续往下执行同步代码,在这之前,then 中回调不会执行。同步代码块执行完毕后,才会在事件循环中检测是否有可用的 promise 回调,如果有,那么执行,如果没有,继续下一个事件循环 -
Promise的升级
1)ES6 出现了 generator 以及 async/await 语法,使异步处理更加接近同步代码写法,可读性更好,同时异常捕获和同步代码的书写趋于一致
2)async/await也是基于 Promise 实现的