跟我一起手撕 Promise

Promise 简介

Promise 是 JS 中异步编程的新解决方案,让我们方便地使用同步地方式书写异步代码。它也被称作 暂未存在确切值的替身

学习 Promise,你要先了解 JS 中的异步编程操作,比如 IO,AJAX,timeout 等。还有 js 事件循环模型。

Promise 出现前,获取异步任务的返回值,是通过设置回调函数。

下面假设了 3 个异步任务,任务之间存在依赖关系,每个异步请求的参数需要上一个异步任务返回的结果值。

1
2
3
4
5
6
7
getData(params,function(data,err){
getSomeData(data,function(someData,err){
getTotalData(someData,function(totalData,err){
console.log('I have got the totalData' + totalData)
})
})
})

以特定执行顺序编排的异步任务,若使用纯回调函数来处理,会出现层层嵌套的书写格式。

我们用 Promise 改写一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new Promise((resolve,reject)=>{
getData(params,function(data,err){
resolve(data)
})
}).then(value=>{
return new Promise((resolve,reject)=>{
getSomeData(value,function(data,err){
resolve(data)
})
})
}).then(value=>{
return new Promise((resolve,reject)=>{
getTotalData(value,function(data,err)=>{
resolve(data)
})
})
}).then(value=>{
console.log('I have got the totalData' + value)
}).catch(err=>{
console.log(err)
})

只需要在最后 catch 一下,就能捕获到上述每个环节的错误。

ES8 还提供了异步函数语法 async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 假设 promisify 是将异步任务转变为 promise 风格的函数
const data = promisify(getData)
const someData = promisify(getSomeData)
const totalData = promisify(getTotalData)
// 立即执行的 async 函数
;(async ()=>{
try{
const param1 = await data(params)
const param2 = await someData(param1)
const total = await totalData(param2)
console.log('I have got the totalData' + total)
}catch(e){
console.log(e.message)
}
})()

Promise 的特点

解耦了回调函数 ⭐⭐⭐⭐

promise 使用 then 方法指定回调函数

跟以前只要定义异步任务就一定要传入回调函数相比,简直就是新奇。

链式调用

Promise 的 then 方法会返回新的 Promise 实例,供我们继续调用 then 方法指定下一个回调函数。

我们可以组成一条 Promise 链,串连我们的异步任务。

异常穿透

单个 Promise 无需设置错误回调函数,在 Promise 链的末尾指定错误回调(onRejected),就能捕获到 Promise 链上出现的错误。

总结起来,Promise 的三个特点:解耦回调函数、异常穿透、链式调用

Promise 的内部实现

Promise 本质上是一个构造函数,通过传入执行器(executor)构造 Promise 对象,这个过程也称为封装异步操作。

要对 7 个问题有所认识。

  1. Promise 如何实现状态转换
  2. Promise 是先改状态再指定回调,还是先指定回调再改状态
  3. Promise 对象指定多个成功的回调,在状态改变时它们都会调用吗
  4. then 方法返回的 Promise 对象,其状态是由什么来决定的。
  5. Promise 如何实现链式调用
  6. Promise 如何实现异常穿透
  7. Promise 链如何中断

Promise 如何实现状态的转换

Promise 通过 3 种方式实现状态转换:

调用 resolve() 将状态改为 fulfilled
调用 reject() 将状态改为 rejected
throw 一个值,将状态改为 rejected

上述操作都在执行器函数(executor)中发生,并且 promise 的状态只能改变一次,改变状态的同时也会设置 promise 的结果值。

Promise 是先改状态再指定回调,还是先指定回调再改状态

Promise 既可以先改变状态,也可以先指定回调,这都不会影响运行 Promise 以及获取结果值。

Promise 对象指定多个成功的回调,在状态改变时它们都会调用吗

它们都会调用

then 方法返回的 Promise 对象,其状态是由什么来决定的。

由执行的回调函数的返回值决定:

回调函数返回非 Promise 对象,then 方法返回 fulfilled 的 Promise

回调函数返回 Promise 对象,then 方法返回的 Promise 拥有相同的状态和结果

回调函数 throw 一个值,then 方法返回 rejected 的 Promise

Promise 如何实现链式调用

调用 then 方法返回封装了新的异步任务的 Promise

Promise 如何实现异常穿透

当没有给 then 方法提供失败回调(onRejected)时,then 方法内部存在一个默认失败回调 reason => {throw reason},让 then 方法返回失败的 Promise,沿着 Promise 链传递到最后。

因此我们只需要给最后的一个 promise 指定失败回调即可捕获链上 Promise 的错误。

Promise 链如何中断

返回一个永久 pending 状态的 Promise 对象