我始终认为理解一个知识总需要先知道一个知识的使用与作用,也因此本文分为两部分,篇幅较长,对 Promise 的使用比较熟悉可以选择直接略过第一部分直接阅读手写部分,可以减少一些阅读的时间成本
Promise 使用详解
没有 Promise 怎么解决异步请求
-
俗话说的好,没有对比就没有伤害,所以我们通过一个案例来认识一下没有 Promise 怎么解决这个异步请求,如下:
// 定义一个请求远程数据的方法,并用定时器模拟异步 function requests(id, callback) { // 根据参数发送请求,这里使用打印模拟 console.log(`携带${id}参数发送请求`) // 使用定时器三秒后接收请求数据结果 setTimeout(() => { // 请求成功后调用回调 callback({ name: '张三', age: 18, address: '杭州' }) }, 3000) } requests(1, foo) // 请求数据成功执行的回调函数 // - 通常这时候我们会根据数据执行一些操作 function foo(data) { console.log(`${data.name}今年${data.age}岁了, 在${data.address}上学`) }
-
上述代码实现的功能就是调用 requests 方法,参数一接收一个 id, 参数二接收一个回调函数,模拟根据 id 获取学生信息,获取到数据后调用回调函数执行需要的逻辑,目前可能看着觉得还好是吧,一个回调也能接受
-
那如果我们的业务需要根据上一次的请求结果来决定下一次的请求结果呢,而这种嵌套又不止一层呢,我们在来看一下下面这个案例,如下:
// 需求: // - 1、添加一个学生信息,返回一个id // - 2、根据这个 id 去给学生分配班级和班主任 // - 3、成功后告诉学生班级地址去报道 function requests(info) { console.log(info, '发送添加学习信息请求') setTimeout(() => { const stuId = 70437 // 根据学生id分配班级和班主任 console.log(stuId, '根据学生id分配班级和班主任, 并将分配好的选择发送请求') setTimeout(() => { console.log({ address: '初中部思德楼2011室' }, '成功返回班级地址') console.log('~执行通知学生代码逻辑~') }, 1000) }, 1000) } requests({ name: '张三', age: 14 })
-
这个案例只是演示随意编写的,在实际开发中,可能层级不止我们现在这么一点层级,但是通过上述的代码也能管中规豹,能够发现这种回调过多会导致代码层级越来越深,也就是我们俗称的
回调地狱
,这种代码可读性差,可维护性也非常的差,而这样也会大量的增加这个项目后续维护的成本 -
可能这时候就会想到,我可以像第一个案例那样,将这个回调函数提取出来呢,这样也是可以的。我们先来看一下提取出来的结果,如下:
function requests(info, callback) { console.log(info, '发送添加学习信息请求') setTimeout(() => { callback(70437, fn2) }, 1000) } requests({ name: '张三', age: 14 }, fn1) function fn1(stuId, callback) { console.log(stuId, '根据学生id分配班级和班主任, 并将分配好的选择发送请求') setTimeout(() => { callback('初中部思德楼2011室') }, 1000) } function fn2(address) { console.log(address, '~执行通知学生代码逻辑~') }
-
这么一看好像确实解决了上面层级过深的问题,但是这种代码实际开发下来他的阅读性也是非常差的,也会增加我们的开发成本,需要时刻注意回调参数的传递,中间某个环节出现问题,调试成本也会增加,具体大家可以自己多写几个层级感受一下
-
通过这种方式不是不能实现,只是这种实现方式不好,也因此 Promise 应运而生
Promise 的基本使用
-
在创建一个 Promise 时,我们需要传入一个回调函数,这个回调函数我们一般称之为 excutor(执行者),传入的这个回调函数会立即执行,我们可以先测试一个这个函数是否会被立即执行,如下:
const p = new Promise(() => { console.log('函数被执行') })
-
测试结果如图:
-
这个回调函数具备两个参数,
resolve
、reject
,这两个参数也是函数,一个表示成功时执行的回调,一个表示失败时执行的回调,通过这一步我们可以写出如下代码:const p = new Promise((resolve, reject) => { console.log('函数被执行') })
-
而这两个参数有什么作用呢,在执行 resolve 的时候,我们就可以通过 then 方法去获取这个数据,使用这个 then 方法是时候可以在传入一个回调函数,这个回调函数里面就可以获取到这个数据,而当在 executor 函数内部 resolve 函数被调用的时候,外部 then 方法的回调函数就会被执行,如下:
const p = new Promise((resolve, reject) => { resolve('success') }) p.then(res => { console.log('res 回调: ', res) // res 回调: success })
-
同理执行 reject 就会执行 catch 方法,如下:
const p = new Promise((resolve, reject) => { reject('fail') }) p.catch(err => { console.log('err 回调: ', err) // err 回调: fail })
-
这里可以顺带提一句,在 Promise A+ 规范中其实是没有规定这些 catch 方法的,这些方法只是在 JavaScript 中增加了便于开发,规范中只约束了 then 方法,在规范中 then 方法有两个参数,参数一是成功时执行,参数二失败时执行,所以失败和成功是可以在一个 then 方法中调用的,如下:
// ------------------------ resolve ------------------------ const p = new Promise((resolve, reject) => { resolve('success') }) p.then( res => { console.log('成功:', res) // 成功: success }, err => { console.log('失败:', err) } ) // ------------------------ reject ------------------------ const p = new Promise((resolve, reject) => { reject('fail') }) p.then( res => { console.log('成功:', res) }, err => { console.log('失败:', err) // 失败: fail } )
-
在这种机制下和规范的约束下,就会减少我们的开发成本和减少沟通成本,也可以提升代码的可读性,可维护性
Promise 的三种状态
-
Promise 具备三种状态:
pending(进行中)、fulfilled(已完成)和 rejected(已拒绝)
-
最开始执行 executor 时状态为 pending,执行 resolve 时就会将状态修改为 fulfilled,执行 reject 时就会将状态修改为 rejected,
且状态一旦从 pending 修改为其他状态就无法再次被修改
,这个我们也可以测试一下:new Promise((resolve, reject) => { resolve(111) reject(222) }).then( res => { console.log(res) // 111 }, err => { console.log(err) } )
-
而这种不能修改状态,
不代表后面的代码不能执行
,只是无法再调用 resolve 或者 rejec 来改变状态了,如下:new Promise((resolve, reject) => { console.log('++++++++') resolve(111) reject(222) console.log('--------') }).then( res => { console.log(res) // 111 }, err => { console.log(err) } )
-
输出如图:
-
先调用 reject ,那么 resolve 就不能在执行了,这个就不在演示了
使用 Promise 优化异步请求
-
Promise 当然还有很多细节,这里简单介绍一下之后就可以进行一些使用了,先看一下优化完成后的代码结构,如下:
const p = new Promise((resolve, reject) => { console.log('发送添加学习信息请求') setTimeout(() => { resolve(70437) }, 1000) }) p.then(res => { console.log(res, '根据学生id分配班级和班主任, 并将分配好的选择发送请求') return new Promise((resolve, reject) => { setTimeout(() => { resolve('初中部思德楼2011室') }, 1000) }) }).then(res => { console.log(res, '成功返回班级地址') console.log('~执行通知学生代码逻辑~') })
-
这个写法涉及到一个链式调用,后续会讲解,这里先看一个用法,通过这个方式就解决了层级过深的问题,也可以不需要提取那么多的函数来增加我们的开发成本,可阅读性也是有所提升,当然调试可能还不是那么方便,不过这并不是 Promise 最终使用方案,后面会有更好的解决方案
-
因为存在这样的一个规范,我们看见 resolve 就知道他会执行 then 的成功回调,也是因为在这种规范下,我们可以不用再去花费精力维护一些回调函数,而这些在一些 api 中也有非常明显的体现,只要看见这个 api 返回的是一个 Promise,那么就不需要去看源码或者文档就可以知道这个 api 如何使用
-
可能现在并没有感受出来这个 Promise 带来的好处,还觉得有点麻烦,不用担心,接着往后看
Promise 的 resolve 参数详解
-
我们先看一下不传递参数和传递一个普的参数结果,如下:
// resolve 不传递值-返回 undefined new Promise((resolve, reject) => { resolve() }).then(res => { console.log(res) // undefined }) // resolve 传递普通的值 Number String Object Array new Promise((resolve, reject) => { resolve('hello world') }).then(res => { console.log(res) // hello world })
-
这两种结果很好理解,没有什么特殊的地方,我们看看如果传递一个 Promise 会咋样,如下:
new Promise((resolve, reject) => { resolve(new Promise((resolve, reject) => {})) }).then(res => { console.log('res: ', res) })
-
这段代码执行就会发现什么都没有执行成功,如图:
-
那为什么没有执行呢? 因为最开始是由 pending —> fulfilled,从等待到已完成的状态,但是由于 resolve 传递的是一个 Promise,所以这个
状态的决定权就交给了这个新传入的 Promise
,Promise 的状态,而这个新的 Promise 并没有确定状态,所以就什么都不会执行 -
传递参数还有一种比较特殊的情况:
传递一个对象,且这个对象实现了 then 方法
,那么执行的就是此 then 方法,如下:new Promise((resolve, reject) => { const obj = { name: '张三', // 具备 then 方法 then: function () { console.log('自定义的 then 方法:', `我是${this.name}`) // 自定义的 then 方法: 我是张三 } } resolve(obj) }).then(res => { console.log('res: ', res) })
-
而且
后续的状态由此 then 方法决定
,如下:new Promise((resolve, reject) => { const obj = { name: '张三', then: function (resolve, reject) { reject('自定义方法修改为失败状态') } } resolve(obj) }).then( res => { console.log('res: ', res) }, err => { console.log('err: ', err) // err: 自定义方法修改为失败状态 } )
-
这种对象实现了一个 then 方法的现象也被称之为 thenable
Promise 其他 API 的详解
then
-
关于 then 方法在前面已经简单讲过一部分了,不过显然不是全部,所以这里还需要再次解析一下
-
then 方法可以接收两个参数这个前面就说过了,而 then 方法本身是存在于 Promise 原型身上的方法
-
需要查看的话可以通过 Object*.getOwnPropertyDescriptors(Promise.*prototype) 去查看,直接打印原型不能查看是因为通过属性描述符将可枚举配置设置了 false
-
一个 Promise 的 then 方法是可以
被多次调用
的,如下:const p = new Promise((resolve, reject) => { resolve(111) }) p.then(res => { console.log(res) // 111 }) p.then(res => { console.log(res) // 111 }) p.then(res => { console.log(res) // 111 })
-
这种多次调用不同于链式调用,链式调用使用的是上一个 Promise 的返回值,即他们处于不同的 Promise
-
then 方法本身是
具备返回值的
,注意了,这个返回值是一个 Promise 对象,正因为返回的是一个 Promise 所以才能链式调用,这就是 Promise 的本质,返回值一般有三种情况:-
情况一:一个普通的值,比如字符串,数字,一个普通的对象和数组,等于返回的时候内部包裹了一层 Promise,并调用 resolve 方法,当然不一定就是 resolve 方法,这个后面手写的时候就清楚了,可以测试一下。如下:
const p = new Promise((resolve, reject) => { resolve(111) }) p.then(res => { console.log('res1:', res) // res1: 111 return 222 }).then(res => { console.log('res2:', res) // res2: 222 return 333 }).then(res => { // 此处不返回 // - 没有返回值就是等于一个函数没有返回值的情况一样,返回一个 undefined console.log('res3:', res) // res3: 333 }).then(res => { // 接收的值是 undefined console.log('res4:', res) // res4: undefined })
-
情况二:返回的是一个 Promise,如下:
const p = new Promise((resolve, reject) => { resolve(111) }) p.then(res => { console.log('res1:', res) // res1: 111 return new Promise((resolve, reject) => { resolve('new promise') }) }).then(res => { console.log('res2:', res) // res2: new promise }) // 此处返回一个 Promise,那么 then 方法本身返回一个值的时候也会再次包裹一层 Promise // - 步骤解析: // 1、then ---> 返回值是一个 Promise,即 new Promise((resolve, reject) => { resolve('new promise')}),使用变量 p2 代替 // 2、那么 than 方法再次包裹一层 Promise,我们就会得到一个这样的等式,new Promise((resolve, reject) => { resolve(p2)}) // 3、由于 then 方法包裹的这层 Promise 的 resolve 接收的参数是一个 Promise,是不是满足我们之前提到的 resolve 参数的值为 Promise 时的情况 // 4、因为 resolve 的参数为 Promise 的话,这个传入的 p2 这个 Promise 就会决定这个 then 方法本身包裹这一层 Promise 的状态 // 5、由于是由 p2 决定的,所以后续就会输出 'new promise' 字符串
-
情况三:返回一个对象且实现 then 方法(thenable),如下:
p.then(res => { console.log('res1:', res) // res1: 111 const obj = { then: function (resolve, reject) { resolve('thenable') } } return obj }).then(res => { console.log('res2:', res) // res2: thenable }) // 这个现象和返回一个 Promise 一致,满足 resolve 参数的是 thenable 的情况
-
-
至于为什么 resolve 就可以有这些现象在后面手写之后就会理解
catch
-
之前有提到过 then 方法的第二个参数就是错误捕获,这也是 Promise a+ 规范的规范写法,但是这样不够语义化,不是很明确,所以在 JavaScript 就新增了一个 catch 用来捕获
拒绝状态
的错误 -
先来看一下基本正常调用 rejecte 的使用,如下:
const p = new Promise((resolve, reject) => { reject('发生了一个错误~') }) p.catch(err => { console.log(err) // 发生了一个错误~ })
-
除了直接调用 reject 会触发 catch 错误回调之外,还可以通过抛出一个错误来触发,如下:
const p = new Promise((resolve, reject) => { throw new Error('抛出了一个错误~~') }) p.catch(err => { console.log(err.message) // 抛出了一个错误~~ })
-
这个接收之后的错误也不会影响后续代码的执行,这个 catch 还有一种写法,如下:
const p = new Promise((resolve, reject) => { throw new Error('抛出了一个错误~~') }) p.then(res => { console.log(res) }).catch(err => { console.log(err.message) // 抛出了一个错误~~ })
-
then 方法虽然也会返回一个 Promise,但是上述写法的 catch 实际对应的还是最开始的 Promise,这是一种语法糖,并且也可以捕获上面 then 方法的错误,如下:
const p = new Promise((resolve, reject) => { resolve(111) }) p.then(res => { console.log(res) throw new Error('then error') }).catch(err => { console.log(err.message) // then error })
-
但是如果两者独立调用则无法处理错误,如下:
const p = new Promise((resolve, reject) => { reject('new error~') }) p.then(res => { console.log(res) }) p.catch(err => { console.log(err.message) })
-
这样两次调用就无法捕获错误,这样调用等于两个方法独立调用,知道 then 方法时就会检测到没有方法可以捕获异常
finally
-
这个方法表示 Promise 无论是 fulfilled 的还是 reject 的最终都会变成被执行的代码
-
finally 方法是不接收参数的,因为最终都是会执行的,如下:
const p = new Promise((resolve, reject) => { resolve(111) }) p.then(res => { console.log('res:', res) // res: 111 }).catch(err => { console.log('err:', err) }).finally(() => { // 无参数 console.log('finally') // finally })
resolve
-
在已经存在一个值的情况下,将这个值转成 Promise,如果还要特意 new 一个 Promise 在调用 resolve 方法就比较麻烦了
-
因此在 JavaScript 中提供了一个 Promise 的静态方法 resolve,如下:
const obj = { name: 'zs', age: 18 } const pObje = Promise.resolve(obj) console.log(pObje) // Promise { { name: 'zs', age: 18 } } pObje.then(res => { console.log(res) // { name: 'zs', age: 18 } }) const p = Promise.resolve( new Promise((resolve, reject) => { resolve(111) }) ) p.then(res => { console.log(res) // 111 }) const pThenable = Promise.resolve({ then: function (resolve) { resolve('thenable') } }) pThenable.then(res => { console.log(res) // thenable })
reject
-
reject 方法与 resolve 方法类似,都是静态方法,只是返回一个拒绝状态,如下:
const error = 'get data fail~' const p = Promise.reject(error) p.catch(err => { console.log(err) // get data fail~ })
-
其他的大家可以自行测试
all
-
当在进行某些请求的时候,你可能发送多个请求,且需要等待所有的结果都成功后才能执行下一步操作,此时 Promise 的 all 静态方法就可以实现,所有 Promise 都变成 fulfilled 时在获取结果,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) // - 参数是一个数组,存放 Promise // - 如果存放的值不是 Promise,则会转为 Promise,会做一个操作 Promise.resolve(val) Promise.all([p1, p2, p3, 'abc']).then(res => { console.log(res) // [ 111, 222, 333, 'abc' ] })
-
但是如果在 all 拿到所有 Promise 的成功结果之前,如果有一个 Promise 的状态为 rejected,那么就不会执行 then 方法的成功回调,就会执行执行失败的回调,并返回这个失败的值,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject(222) // 将其中的一个 Promise 改为 reject 状态 }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) Promise.all([p1, p2, p3]) .then(res => { console.log(res) }) .catch(err => { console.log(err) // 222 })
-
且当出现一个 rejected 状态的时候就会停止 all 方法,不在获取其他 Promise 的值,因此只会返回所有 Promise 中第一个 rejected 状态的值
-
而这个数组输出的值顺序,与存放的顺序有关,与执行顺序无关,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) Promise.all(['abc', p2, p3, p1]).then(res => { console.log(res) // [ 'abc', 222, 333, 111 ] })
allSettled
-
all 方法存在一个缺陷,当有一个 Promise 变成 rejected 状态时,就不会再次获取其他 Promise 的值,哪怕后续的 Promise 存在 fulfilled 状态,只是当前是 pending 状态,也获取不到对应的结果
-
allSettled 方法会等待所有 Promise 都有一个结果,无论这个结果是 fulfilled 还是 rejected,才会得到最终的状态,并且不管里面是否存在失败的结果,都只会执行成功的回调,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject(222) // 将其中的一个 Promise 改为 reject 状态 }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { reject(333) }, 3000) }) Promise.allSettled([p1, p2, p3]).then(res => { console.log(res) /* [ { status: 'fulfilled', value: 111 }, { status: 'rejected', reason: 222 }, { status: 'rejected', reason: 333 } ] */ })
-
顺序与存放顺序相关
race
-
race 方法返回的是第一个完成的 Promise,即在所有的 Promise 中,第一个确定状态的 Promise 就会在 race 中执行,不管状态是 fulfilled 还是 rejected,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) Promise.race([p1, p2, p3]) .then(res => { console.log('res:', res) }) .catch(err => { console.log('err:', err) })
-
更换状态测试,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) Promise.race([p1, p2, p3]) .then(res => { console.log('res:', res) }) .catch(err => { console.log('err:', err) })
any
-
any 方法至少会等到一个 fulfilled 的结果,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(333) }, 3000) }) Promise.any([p1, p2, p3]) .then(res => { console.log('res:', res) // res: 222 }) .catch(err => { console.log('err:', err) })
-
如果所有的 Promise 的结果都是 rejected 会得到什么,我们一起看一下,如下:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { reject(111) }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject(222) }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { reject(333) }, 3000) }) Promise.any([p1, p2, p3]) .then(res => { console.log('res:', res) }) .catch(err => { console.log('err:', err) /* err: [AggregateError: All promises were rejected] { [errors]: [ 111, 222, 333 ] } */ })
-
会得到一个错误信息 All promises were rejected
手写 Promise
前面铺垫这么久终于来到了最激动的时刻,手写 Promise,手写一次 Promise 我个人觉得还是比记 api 的使用和去理解的他的执行过程要好很多,在 Promise A+ 规范中,其实只有构造器以及 then 方法,所以关于 JavaScript 后续增加的 catch、all、any… 等等这些方法,我都会打上选看的标签,有兴趣的可以看一下,没有的话就看一下最核心的前面实现构造器以及 then 方法即可,而实现了 then 方法其他方法也是非常简单的,为了让写起来更加的直观,这里采用 class 来实现,关于 class 的知识这里不再进行额外讲解
Promise 构造器的实现(核心)
-
在前面我们提到过,Promise 在使用的时候需要传入一个回调函数,也叫做执行器(executor),并且这个函数是同步执行,也就意味着会被立即执行,根据这些我们可以得出一个最开始的结构,如下:
class JcPromise { constructor(executor) { executor() } } const p = new JcPromise(() => { console.log('callback 执行') // callback 执行 })
-
实现最基础的结构之后我们就可以来看一下 executor 函数所具备的两个参数,resolve 和 reject,在前面使用 Promise 的时候我们就知道就两个参数是一个函数,并可以接收一个参数,
-
那问题来了,应该怎么来编写这两个函数呢?我们已知在实例化的时候就会执行 constructor 这个构造函数来初始化一些东西,那我们在这里定义两个函数是不是就可以在 executor 执行的时候调用呢 于是我们可以进一步写出如下代码:
class JcPromise { constructor(executor) { const resolve = res => { console.log(res) // success } const reject = err => { console.log(err) } executor(resolve, reject) } } const p = new JcPromise((resolve, reject) => { resolve('success') })
-
可能会有一个疑惑,为什么不在原型上去定义这个函数呢,这样就不会使用 Promise 的时候去重新创建这两个函数了,既然如此我们可以先单独开一些例子来解释这个问题,如下:
class Demo { constructor(func) { func(this.foo) } foo(val) { console.log(this, val) } } new Demo(fn => { fn(1) })
-
这时候大家可以自己思考一下这个 this 会输出什么,按照我们学习的过的知识而言,一个函数的 this 指向取决于它的调用方式,这里属于独立调用,那么它的 this 应该就会指向默认上下文对象即 window,是否真的如此呢,我们可以看一下,结果如图:
-
看到这个 undefined 可能会觉得有一些疑惑,其实也很好解释,这是因为在 class 下
默认是开启严格模式的
,而严格模式下 this 是不能指向 window 的,而是会默认指向 undefined,这一点在 mdn 文档中也作出了解释,class,如图:
-
这里我们可以改用箭头函数或者使用 bind 方法绑定 this 返回一个函数都可以,如下:
class Demo { constructor(func) { // bind func(this.foo.bind(this)) // 箭头函数 func(this.bar) } foo(val) { console.log(this, val) } bar = val => { console.log(this, val) } } new Demo(fn => { fn(1) })
-
this 输出如图:
-
这里给大家讲了一点题外话,不管使用那种方法都应该保证 this 指向的正确性
-
现在我们回到之前的代码结构,我们调用两个参数,看看输出的情况,如下:
class JcPromise { constructor(executor) { const resolve = res => { console.log(this) console.log(res) // success } const reject = err => { console.log(err) // fail } executor(resolve, reject) } } const p = new JcPromise((resolve, reject) => { resolve('success') reject('fail') })
-
可以看到,两个函数都被执行了,但是根据我们前面使用的 Promise 来看,Promise 的状态一旦被确定了,就不能再次被更改,所以两个函数只能执行一个,那怎么才能实现这个效果呢,我们不妨回顾一下 Promise 的三种状态,pending、fulfilled、rejected,有了这三个状态之后我们只需要执行 resolve 和 reject 之前,判断一下当前是否为 pending 啊,如果为 pending 就执行,否则不执行,执行之后修改状态,那么是不是就可以实现我们的需求了
-
根据上面的分析我们需要同当前状态来控制是否执行函数,所以我们需要使用一个变量来存储这个状态,代码如下:
class JcPromise { constructor(executor) { this._status = 'pending' const resolve = res => { if (this._status !== 'pending') return this._status = 'fulfilled' console.log('res:', res) } const reject = err => { if (this._status !== 'pending') return this._status = 'rejected' console.log('err:', err) } executor(resolve, reject) } } const p = new JcPromise((resolve, reject) => { resolve('success') reject('fail') })
-
我们现在来看一下打印的输出结果,如图:
-
然后顺序调换一下我这里就不在测试了,大家自己写的时候可以多测试一下
-
同时,我们可以把需要传递的参数提取出去,如下:
class JcPromise { constructor(executor) { this._status = 'pending' this._result = undefined const resolve = res => { if (this._status !== 'pending') return this._status = 'fulfilled' this._result = res } const reject = err => { if (this._status !== 'pending') return this._status = 'rejected' this._result = err } executor(resolve, reject) } } const p = new JcPromise((resolve, reject) => { resolve('success') })
-
现在我们看一下这段代码,就不难发现,他们之前存在重复代码,所以我们可以把他提取出去,而且这些状态名称我们也不应该写死,这种硬编码的代码后期是不太好维护的,所以我们可以定义三个常量表示三种状态,代码如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } executor(resolve, reject) } // 此函数接收两个值:状态与值 _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value console.log(this._status, this._result) // rejected fail } } const p = new JcPromise((resolve, reject) => { reject('fail') resolve('success') })
-
现在就可以实现状态一旦确定就不可以在更改的问题了,不过还是存在一个问题,如果执行 executor 函数时报错怎么办呢,我们可以抛出一个错误测试一下,这个测试只需要调用的时候抛出一个错误即可,代码如下:
const p = new JcPromise((resolve, reject) => { throw new Error('发生了一个错误') resolve('success') })
-
现在来看一下打印的输出结果,如下:
-
捕获异常我们自然的第一选择就是 trycatch,所以我们在调用 executor 的时候需要包裹一个 trycatch,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { // 如果出现错误,就调用 reject 方法,并将错误作为结果返回 reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value console.log(this._status, this._result) } } const p = new JcPromise((resolve, reject) => { throw new Error('发生了一个错误') resolve('success') })
-
现在在看一下输出的结果,如图:
then 方法回调执行时机实现(核心)
-
then 方法具备两个参数,一个成功时的回调,一个失败时的回调,并且返回值是一个 Promise,基于此,我们可以写出 then 方法的基础结构,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value } // 实现 then 方法 then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => {}) } } const p = new JcPromise((resolve, reject) => { resolve('success') })
-
实现了这个方法之后我们需要思考的问题就变成了什么时候执行
onFulfilled 和 onRejected
这两个回调,这个还是很好解决的,当初始的这个 Promise 状态确定之后,就执行成功或失败的回调,所以我们可以根据当前状态来执行这两个回调,代码如下:const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value } then(onFulfilled, onRejected) { // ------- 根据状态来执行成功或失败的回调 ------- if (this._status === FULFILLED) { onFulfilled(this._result) } else if (this._status === REJECTED) { onRejected(this._result) } return new JcPromise((resolve, reject) => {}) } } const p = new JcPromise((resolve, reject) => { resolve('success') }) p.then( res => { console.log('res:', res) }, err => { console.log('err:', err) } )
-
输出结果如图:
-
现在是同步调用,不会出现什么问题,如果是异步执行呢,再来看一下,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value } then(onFulfilled, onRejected) { console.log('查看此时状态 >>>', this._status) if (this._status === FULFILLED) { onFulfilled(this._result) } else if (this._status === REJECTED) { onRejected(this._result) } return new JcPromise((resolve, reject) => {}) } } const p = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) p.then( res => { console.log('res:', res) }, err => { console.log('err:', err) } )
-
来看一下输出的结果,如图:
-
现在就很明显了,没有输出结果,因为我们是在异步的调用,如果不太明白,我们可以打印测试一下,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined const resolve = res => { console.log('执行 resolve') this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value } then(onFulfilled, onRejected) { console.log('查看此时状态 >>>', this._status) console.log(333) if (this._status === FULFILLED) { onFulfilled(this._result) } else if (this._status === REJECTED) { onRejected(this._result) } return new JcPromise((resolve, reject) => {}) } } const p = new JcPromise((resolve, reject) => { console.log(111) setTimeout(() => { console.log(222) resolve('success') }, 1000) }) p.then( res => { console.log('res:', res) }, err => { console.log('err:', err) } )
-
输出结果如图:
-
可以看到执行 then 方法时, resolve 还没有执行,那么状态也就没有该变,所以不会执行成功的回调,那我们是不是可以换一个思路呢,如果是异步的话直接执行 then 的时候由于状态没有确定所以无法执行成功或失败的回调,但是在
_changeStart 方法中只要状态一旦确定我们就是可以执行的
,所以只要在 _changeStart 方法也调用一次这个执行成功或失败回调函数的逻辑就可以了,不过在 _changeStart 方法中无法拿到这两个回调,所以需要给这两个回调绑定到当前的实例,而且这部分代码肯定是重复的,所以我们可以提取一下,如下:const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined this._handle = {} const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value this._run() } // 定义执行方法 _run() { if (this._status === PENDING) return if (this._status === FULFILLED) { this._handle.onFulfilled(this._result) } else { this._handle.onRejected(this._result) } } then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { // 通过对象的形式保存起来 this._handle = { onFulfilled, onRejected } this._run() }) } } const p = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) p.then( res => { console.log('res:', res) }, err => { console.log('err:', err) } )
-
此时我们在看一下是否可以正常执行成功的回调,如图:
-
同时这个使用对象存储的结果是不够的,因为这个 then 方法可以被多次调用,我们多次调用看一下,代码如下:
p.then( res => { console.log('res1:', res) }, err => { console.log('err1:', err) } ) p.then( res => { console.log('res2:', res) }, err => { console.log('err2:', err) } )
-
输出结果如图:
-
为了解决这个问题,我们应该使用数组存储,改造一下代码,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined // 使用数组存储 this._handles = [] const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value this._run() } // 定义执行方法 _run() { if (this._status === PENDING) return // 循环执行 while (this._handles.length) { // 取出数组的第一项 - 进行解构 const { onFulfilled, onRejected } = this._handles.shift() if (this._status === FULFILLED) { onFulfilled(this._result) } else { onRejected(this._result) } } } then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { this._handles.push({ onFulfilled, onRejected }) this._run() }) } } const p = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) p.then( res => { console.log('res1:', res) }, err => { console.log('err1:', err) } ) p.then( res => { console.log('res2:', res) }, err => { console.log('err2:', err) } )
-
输出结果如图:
then 方法返回值实现(核心)
-
上一步我们只是实现 then 方法的回调执行时机,但是 then 方法的返回值我们是没有去实现的,不过我们根据前面的写法,可以知道在执行的时候是需要用到这个返回的新 Promise 的 resolve 和 reject 回调函数的,因此我们可以一起进行存储,如下:
then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { this._handles.push({ onFulfilled, onRejected, resolve, reject }) this._run() }) }
-
并且在执行 then 方法的回调的时候,我们也需要进行判断,判断当前这个回调是否是一个函数,如下:
_run() { if (this._status === PENDING) return while (this._handles.length) { const { onFulfilled, onRejected } = this._handles.shift() if (this._status === FULFILLED) { // 判断是否是一个函数 if (typeof onFulfilled === 'function') { onFulfilled(this._result) } } else { if (typeof onRejected === 'function') { onRejected(this._result) } } } }
-
我们先来看一下原本的 Promise 是如何处理回调不是一个函数的,如下:
const p = new Promise((resolve, reject) => { resolve(111) }) // 回调时一个函数 p.then( res => { console.log('res1:', res) }, err => { console.log(err) } ).then(res => { console.log('res2:', res) console.log('+++++++++++') }) // 回调不是一个函数 p.then(null, err => { console.log(err) }).then(res => { console.log('res2:', res) })
-
输出结果如图:
-
可以看到不是函数的话就会输出上一次执行成功的结果,返回 undefined 这个就是函数没有返回值返回 undefined,这个是非常基础的了,根据这个结果我们需要对不是一个函数的时候做作出一些处理,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined // 使用数组存储 this._handles = [] const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value this._run() } _run() { if (this._status === PENDING) return while (this._handles.length) { const { onFulfilled, onRejected, resolve, reject } = this._handles.shift() if (this._status === FULFILLED) { // 判断是否是一个函数 if (typeof onFulfilled === 'function') { // 获取函数的返回值返回 const res = onFulfilled(this._result) resolve(res) } // 如果不是一个函数 else { // 返回上一次状态确定的值 resolve(this._result) } } else { if (typeof onRejected === 'function') { onRejected(this._result) } // 如果不是一个函数 else { reject(this._result) } } } } then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { this._handles.push({ onFulfilled, onRejected, resolve, reject }) this._run() }) } } const p = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success~') }, 1000) }) p.then( res => { console.log('res1:', res) return 'hello world' }, err => { console.log('err:', err) } ).then(res => { console.log('++++ 是一个函数 ++++') console.log('res2:', res) console.log('============== 分割 ==============') }) p.then(null, err => { console.log('err:', err) }).then(res => { console.log('---- 不是一个函数 ----') console.log('res2:', res) })
-
输出结果如图:
-
现在在处理一下如果运行中函数执行出现错误进行捕获,还是一样是 trycatch,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined // 使用数组存储 this._handles = [] const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value this._run() } _run() { if (this._status === PENDING) return while (this._handles.length) { const { onFulfilled, onRejected, resolve, reject } = this._handles.shift() if (this._status === FULFILLED) { if (typeof onFulfilled === 'function') { try { const result = onFulfilled(this._result) resolve(result) } catch (error) { reject(error) } } else { resolve(this._result) } } else { if (typeof onRejected === 'function') { const result = onRejected(this._result) reject(result) } else { reject(this._result) } } } } then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { this._handles.push({ onFulfilled, onRejected, resolve, reject }) this._run() }) } } const p = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success~') }, 1000) }) p.then( res => { console.log('res1:', res) throw new Error('抛出一个错误~') }, err => { console.log('err:', err) } ).then( res => { console.log('res2:', res) return 'abc' }, err => { console.log('err2:', err) console.log(111111) } )
-
输出一下运行结果,如图:
-
当然失败时状态也是一样的,所以还是一样提取成一个函数,代码如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined this._handles = [] const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value this._run() } // 提取成一个函数 _execFunc(callback, resolve, reject) { if (typeof callback !== 'function') { // 如果不是一个函数的化就根据状态来执行 resolve 还是 rejected const func = this._status === FULFILLED ? resolve : reject // 并将上一次 Promise 结果的值作为返回值 func(this._result) return } // 如果是一个函数则进行函数执行异常的捕获 try { // 执行正常则是使用当前函数执行的返回值作为下一次 Promise 的值 const result = callback(this._result) resolve(result) } catch (error) { // 出现异常则执行 reject reject(error) } } _run() { if (this._status === PENDING) return while (this._handles.length) { const { onFulfilled, onRejected, resolve, reject } = this._handles.shift() if (this._status === FULFILLED) { this._execFunc(onFulfilled, resolve, reject) } else { this._execFunc(onRejected, resolve, reject) } } } then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { this._handles.push({ onFulfilled, onRejected, resolve, reject }) this._run() }) } } const p = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success~') }, 1000) })
实现判断是否是 Promise 和 进入微队列(核心)
-
在 Promise 中,我们判断一个东西是不是 Promise 是通过判断他是不是一个满足 a+ 规范,在规范中,
不管是一个对象还是一个函数,只要这个函数实现了 then 方法且是一个函数
,就符合 a+ 规范,因此我们利用这一点写一处一个函数来判断是否是一个 Promise,如下:_isPromise(value) { if (value !== null && (typeof value === 'object' || typeof value === 'function')) { // 只要这个值不为 null 且 是一个对象或者是一个函数 // - 并带有 then 方法, then 方法是一个函数,那么就是一个 Promise return typeof value.then === 'function' } return false }
-
将判断函数进行判断 then 方法回调函数的返回值即可,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined // 使用数组存储 this._handles = [] const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _isPromise(value) { if (value !== null && (typeof value === 'object' || typeof value === 'function')) { return typeof value.then === 'function' } return false } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value this._run() } _execFunc(callback, resolve, reject) { if (typeof callback !== 'function') { const func = this._status === FULFILLED ? resolve : reject func(this._result) return } try { const result = callback(this._result) // 判断这个 返回值是否是 Promise if (this._isPromise(result)) { // 如果是 Promise 则调用他本身的 then 方法 // - 这个 Promise 成功就调用 resolve,失败就调用 reject result.then(resolve, reject) return } // 不是 Promise 则将结果返回 resolve(result) } catch (error) { reject(error) } } _run() { if (this._status === PENDING) return while (this._handles.length) { const { onFulfilled, onRejected, resolve, reject } = this._handles.shift() if (this._status === FULFILLED) { this._execFunc(onFulfilled, resolve, reject) } else { this._execFunc(onRejected, resolve, reject) } } } then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { this._handles.push({ onFulfilled, onRejected, resolve, reject }) this._run() }) } } const p = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success~') }, 1000) }) p.then( res => { console.log('res1:', res) return new Promise((resolve, reject) => { resolve(111) }) }, err => { console.log('err:', err) } ).then( res => { console.log('res2:', res) }, err => { console.log('err2:', err) } )
-
输出结果如图:
-
我们可以使用一个 thenable 来测试一下,如下:
p.then( res => { console.log('res1:', res) return { then: function (resolve, reject) { reject('thanable rejected') } } }, err => { console.log('err:', err) } ).then( res => { console.log('res2:', res) }, err => { console.log('err2:', err) } )
-
输出结果如图:
-
为什么这个 thenable 可以呢,其实也很简单,当我们确定这个东西是属于一个 Promise A+ 规范的东西之后,我们就会执行代码
result.then(resolve, reject)
,而这个 then 方法是这个东西本身所携带的,但是注意这里 then 的回调函数传递的是一个 resolve 和 reject,而这个怎么来的大家也都知道,所以这一步就等于是去这些其他的 Promise 里面去执行了一下,然后携带了一个执行的结果出来,而具体在这里面调用的 resolve 和 reject 我们就并不关心了,这才是导致如果是 Promise 或者 thenable 的时候,后续的状态由它们决定的根本原因
,是不是手写一次之后这个为什么就迎刃而解了 -
而一个 then 方法中的
回调执行的时候是一个微任务
,所以我们还需要将 then 方法的 onFulfilled 和 onRejected 两个回调加入到微队列里面,而在 node 环境中与在浏览器环境中加入微队列的方式也不一样,所以需要一个辅助函数来帮助我们判断使用哪一种方式加入到微队列,如下:// 这个函数会将传递进来的函数放入微队列执行 _runMicroTask(fn) { // 如果是 node 环境就判断 process 是否是一个对象,且这个对象的 nextTick 属性是一个函数 if (typeof process === 'object' && typeof process.nextTick === 'function') { // 在 node 中的事件循环 process.nextTick() 就可以进入微队列 process.nextTick(fn) } // 如果是浏览器环境使用 queueMicrotask 进入微队列 else if (typeof queueMicrotask === 'function') { queueMicrotask(fn) } // 在其他环境下,无法依赖环境,只能使用 setTimeout 来进行一个异步调用 else { setTimeout(fn, 0) } }
-
将一个任务加入到微队列是环境提供的能力,所以如果环境不支持就无法加入,现在将函数与我们的之前的代码做一个结合,如下:
const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class JcPromise { constructor(executor) { this._status = PENDING this._result = undefined // 使用数组存储 this._handles = [] const resolve = res => { this._changeStart(FULFILLED, res) } const reject = err => { this._changeStart(REJECTED, err) } try { executor(resolve, reject) } catch (error) { reject(error) } } _isPromise(value) { if (value !== null && (typeof value === 'object' || typeof value === 'function')) { return typeof value.then === 'function' } return false } // 这个函数会将传递进来的函数放入微队列执行 _runMicroTask(fn) { // 如果是 node 环境就判断 process 是否是一个对象,且这个对象的 nextTick 属性是一个函数 if (typeof process === 'object' && typeof process.nextTick === 'function') { // 在 node 中的事件循环 process.nextTick() 就可以进入微队列 process.nextTick(fn) } // 如果是浏览器环境使用 queueMicrotask 进入微队列 else if (typeof queueMicrotask === 'function') { queueMicrotask(fn) } // 在其他环境下,无法依赖环境,只能使用 setTimeout 来进行一个异步调用 else { setTimeout(fn, 0) } } _changeStart(state, value) { if (this._status !== PENDING) return this._status = state this._result = value this._run() } _execFunc(callback, resolve, reject) { // 将 then 方法的回到加入微队列执行 this._runMicroTask(() => { if (typeof callback !== 'function') { const func = this._status === FULFILLED ? resolve : reject func(this._result) return } try { const result = callback(this._result) // 判断这个 返回值是否是 Promise if (this._isPromise(result)) { // 如果是 Promise 则调用他本身的 then 方法 // - 这个 Promise 成功就调用 resolve,失败就调用 reject result.then(resolve, reject) return } // 不是 Promise 则将结果返回 resolve(result) } catch (error) { reject(error) } }) } _run() { if (this._status === PENDING) return while (this._handles.length) { const { onFulfilled, onRejected, resolve, reject } = this._handles.shift() if (this._status === FULFILLED) { this._execFunc(onFulfilled, resolve, reject) } else { this._execFunc(onRejected, resolve, reject) } } } then(onFulfilled, onRejected) { return new JcPromise((resolve, reject) => { this._handles.push({ onFulfilled, onRejected, resolve, reject }) this._run() }) } } setTimeout(() => { console.log(111) }, 0) console.log(222) const p = new JcPromise((resolve, reject) => { resolve('success~') console.log(333) }) p.then(res => { console.log(444, res) }) console.log(555)
-
如果添加成功按照执行的打印顺序应该是 2、3、5、4、1,node环境下输出结果如图:
-
在将代码放入到浏览器执行看一下,如图:
-
至此,我们已经将 Promise 最核心的构造器以及 then 方法都实现了,其余的那些 api 就可以选择性的查看
实现 catch 方法(选看)
-
catch 方法在前面使用的时候无非就是进行一个捕获,而捕获错误这个功能其实我们已经是实现了的,所以换而言之我们的 catch 方法是不是只要内部调用一次 then 方法,并且第一个参数传递一个 null,第二个参数传递一个捕获错误的回调函数是不是就可以捕获错误了,代码如下:
catch(onRejected) { return this.then(null, onRejected) }
-
将上述 catch 方法加入到我们实现的 Promise 类中即可,现在来看一下测试代码,如下:
const p = new JcPromise((resolve, reject) => { reject('An unknown error occurred~') }) p.then(res => { console.log('res:', res) }) .catch(err => { console.log('catch1:', err) throw new Error('message err') }) .catch(err => { console.log('catch3:', err) console.log('-----') }) p.catch(err => { console.log('catch2:', err) })
-
测试结果如图:
-
为什么可以传递下来这个也很简单吧,我们没有传递 then 的第二个参数,所以在我们的逻辑中就会判断这个参数不是一个函数,不是一个函数状态为 rejected 的时候就会自动调用一次 reject 方法,调用 resolve 就会被下一个错误捕获,这个逻辑非常简单了,图解如下:
-
这里关于 catch 的详细介绍和定义可以查阅文档:catch
-
其他附加的这些 api 都是基于我们已经写好的 then 实现的,所以只要完成了 then 方法,其他的 api 也就呼之欲出了
实现 finally 方法(选看)
-
这个前面我们也提到过了,只是用于 Promise 结束的时候调用通知一次,并不关心失败或者成功的结果是什么,所以不需要传递参数,所以我们只需要调用一次 then 方法,在失败或者成功的时候都执行一次 finally 的回调就可以了,且因为 finally 是一个透明的,即状态穿透,他的返回结果应该和上一次 Promise 的结果保持一致,如下:
finally(onFinally) { return this.then( res => { onFinally() return res }, err => { onFinally() throw err } ) }
-
测试代码如下:
const p = new JcPromise((resolve, reject) => { resolve('success') }) p.then(res => { console.log('res:', res) return 111 }) .finally(() => { console.log('执行完成 res') }) .then(res => { console.log('finally之后:', res) })
-
结果输出如图:
-
这些都可以在 mdn 中所查到,这里还是简单的贴一下文档地址:finally
-
这里我们和原生的 Promise 的结果来做一个测试对比,如图:
实现静态方法 resolve 和 reject(选看)
-
在 class 中实现一个静态方法需要使用 static 关键字定义,所以我们可以先写出这个函数,如下:
static resolve() {} static reject() {}
-
这个还是非常简单的,resolve 和 reject 无非就是包裹一层 Promise,接收一个参数,并确定状态使用一下即可,如下;
static resolve() { return new JcPromise((resolve, reject) => { resolve(res) }) } static reject() { return new JcPromise((resolve, reject) => { reject(err) }) }
实现静态方法 all 和 allSettled(选看)
-
all 方法返回一个 Promise,并等待所有的的 Promise 都成功后再执行,只要任何一个输入的 promise 的 reject 回调执行或者输入不合法的 promise 就会立即抛出错误,并且 reject 的是第一个抛出的错误信息,所以我们可以得出一个基本函数的结构,如下:
static all(arr) { // 返回一个 Promise return new JcPromise((resolve, reject) => { const newArr = [] arr.forEach((item, index) => { item.then( res => { newArr.push(res) if (newArr.length === arr.length) { // 当数组长度相等表示都执行完毕 resolve(newArr) } }, err => { // 出现一个错误即立即抛出 reject(err) } ) }) }) }
-
当然这里面需要判断一个值是否是 Promise,且输出的结果顺序应该根据传递的数组来决定,所以上面这里还是不太符合我们的需求,可以改造一下,如下:
static all(arr) { const pObj = {} // 是为了使用 _isPromise 方法来判断一个值是否是 Promise // - 当然更好的选择其实是将这个工具函数提到 class 外部使用,这里偷懒就不在提取了 const _that = new JcPromise((resolve, reject) => {}) return new JcPromise((resolve, reject) => { arr.forEach((item, index) => { if (_that._isPromise(item)) { item.then( res => { pObj[index] = res const len = Object.keys(pObj).length if (len === arr.length) { const newArr = [] for (const key in pObj) { newArr[key] = pObj[key] } resolve(newArr) } }, err => { reject(err) } ) } else { JcPromise.resolve(item).then(res => { pObj[index] = res }) } }) }) }
-
上面定义成一个对象,主要是为了实现输出的结果与传入的数组元素顺序保持一致,原理也很简单,利用索引值为 key,输出的时候重新排序,当然还有一些 ec 这里并没用做,不过大体已经差不多了
-
还是老样子最后加上测试代码,如下:
const p1 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p1') }, 2000) }) const p2 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('p2 错误') }, 1000) }) const p3 = new JcPromise((resolve, reject) => { resolve('success p3') }) JcPromise.all([p1, p2, 'abc', p3]) .then(res => { console.log('all res: ', res) }) .catch(err => { console.log('all err: ', err) })
-
输出结果如图:
-
在换一下测试代码,如下:
const p1 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p1') }, 2000) }) const p2 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p2') }, 1000) }) const p3 = new JcPromise((resolve, reject) => { resolve('success p3') }) JcPromise.all([p1, p2, 'abc', p3]) .then(res => { console.log('all res: ', res) }) .catch(err => { console.log('all err: ', err) })
-
输出结果如图:
-
可以看到,在这段代码里面输出的数组顺序并不由 Promise 执行的先后顺序决定,非 Promise 的值也可以正常输出,最后还是贴上文档地址:all
-
实现了 all,allSettled 方法其实很简单,这里关于判断是否是 Promise 和 保证输出结果的顺序就不在写了,和 all 的方法一样,allSettled 只会执行成功的回调,然后等待所有的 Promise 执行完成,那我们只需要把传入的 promise 都拿到结果在返回即可,只是数组元素是一个对象,如下:
static allSettled(arr) { const newArr = [] return new Promise((resolve, reject) => { arr.forEach(item => { item .then( res => { newArr.push({ status: 'fulfilled', value: res }) }, err => { newArr.push({ status: 'rejected', reason: err }) } ) .finally(() => { if (newArr.length === arr.length) { resolve(newArr) } }) }) }) }
-
这个都是前面解析过的,就不在解释了,添加一下测试代码,如下:
const p1 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p1') }, 2000) }) const p2 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('p2 错误') }, 1000) }) const p3 = new JcPromise((resolve, reject) => { resolve('success p3') }) JcPromise.allSettled([p1, p2, p3]) .then(res => { console.log('all res: ', res) }) .catch(err => { console.log('all err: ', err) })
-
结果如图:
-
文档地址:allSettled
实现静态方法 any 和 race(选看)
-
any 方法会先得到一个最先状态变为 fulfilled 状态的结果,如果所有的 promise 都状态都是 rejected,则会返回一个错误
AggregateError: All promises were rejected
,当然,如果是一个空数组也会返回这个错误,不过这是一个实验性的特性,还并不是那么稳定 -
any 方法的大体实现也是和之前的 all 类似,只不过是返回的顺序和结果有着一些差别而已,如下:
static any(arr) { return new JcPromise((resolve, reject) => { if (!arr.length) return reject('AggregateError: All promises were rejected') const newArr = [] arr.forEach(item => { item.then( res => { // 只要有一个 Promise 成功,则直接返回成功的结果 resolve(res) }, err => { // 如果是失败则计数,或者将失败的结果加入到一个新数组,然后继续等待 // - 如果计数或者数组长度等于传入的 promise 数组长度则表示全部失败 newArr.push(err) if (newArr.length === arr.length) { reject('AggregateError: All promises were rejected') } } ) }) }) }
-
下面我们就进行测试一下,代码如下:
const p1 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('fail p1') }, 2000) }) const p2 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('fail p2') }, 1000) }) const p3 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('fail p3') }, 1500) })
-
输出结果如图:
-
在修改一下测试代码。如下:
const p1 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p1') }, 2000) }) const p2 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('p2 成功~') }, 1000) }) const p3 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('success p3') }, 1500) })
-
按照上面的执行时间,我们期望输出的是 p2 的值,结果如图:
-
其余更多的测试大家自己写的时候可以多测试一下,暴力测试一下也无所谓,文档地址:any
-
race 见名知意,竞赛,也就是只返回第一个完成的 Promise,不管是 fulfilled 状态还是 rejected 状态,那就非常好写了,执行所有的 promsie,只要有结果就直接返回,如下:
static race(arr) { return new JcPromise((resolve, reject) => { arr.forEach(item => { item.then( res => { resolve(res) }, err => { reject(err) } ) }) }) }
-
测试代码如下:
const p1 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p1') }, 2000) }) const p2 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p2') }, 1500) }) const p3 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('fail p3') }, 500) })
-
按照执行的先后,我们期望输出 p3 的值,如图:
-
改变一下测试代码,如下:
const p1 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p1') }, 2000) }) const p2 = new JcPromise((resolve, reject) => { setTimeout(() => { resolve('success p2') }, 1500) }) const p3 = new JcPromise((resolve, reject) => { setTimeout(() => { reject('fail p3') }, 2500) })
-
现在所期望得到的值就是 p2 的值,如图:
-
文档地址:race
源码
至此,Promise a+ 规范的部分和 JavaScript 中新增的 api 部分我们都已经实现了一次,篇幅较长,感谢观看!
因为后续测试的时候方法都是单独展示的,所以在这里贴出整个实现的代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class JcPromise {
constructor(executor) {
this._status = PENDING
this._result = undefined
this._handles = []
const resolve = res => {
this._changeStart(FULFILLED, res)
}
const reject = err => {
this._changeStart(REJECTED, err)
}
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
_isPromise(value) {
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
return typeof value.then === 'function'
}
return false
}
_runMicroTask(fn) {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(fn)
}
else if (typeof queueMicrotask === 'function') {
queueMicrotask(fn)
}
else {
setTimeout(fn, 0)
}
}
_changeStart(state, value) {
if (this._status !== PENDING) return
this._status = state
this._result = value
this._run()
}
_execFunc(callback, resolve, reject) {
this._runMicroTask(() => {
if (typeof callback !== 'function') {
const func = this._status === FULFILLED ? resolve : reject
func(this._result)
return
}
try {
const result = callback(this._result)
if (this._isPromise(result)) {
result.then(resolve, reject)
return
}
resolve(result)
} catch (error) {
reject(error)
}
})
}
_run() {
if (this._status === PENDING) return
while (this._handles.length) {
const { onFulfilled, onRejected, resolve, reject } = this._handles.shift()
if (this._status === FULFILLED) {
this._execFunc(onFulfilled, resolve, reject)
} else {
this._execFunc(onRejected, resolve, reject)
}
}
}
then(onFulfilled, onRejected) {
return new JcPromise((resolve, reject) => {
this._handles.push({ onFulfilled, onRejected, resolve, reject })
this._run()
})
}
catch(onRejected) {
return this.then(null, onRejected)
}
finally(onFinally) {
return this.then(
res => {
onFinally()
return res
},
err => {
onFinally()
throw err
}
)
}
static resolve(res) {
return new JcPromise((resolve, reject) => {
resolve(res)
})
}
static reject(err) {
return new JcPromise((resolve, reject) => {
reject(err)
})
}
static all(arr) {
const pObj = {}
return new JcPromise((resolve, reject) => {
arr.forEach((item, index) => {
if (this._isPromise(item)) {
item.then(
res => {
pObj[index] = res
const len = Object.keys(pObj).length
if (len === arr.length) {
const newArr = []
for (const key in pObj) {
newArr[key] = pObj[key]
}
resolve(newArr)
}
},
err => {
reject(err)
}
)
} else {
JcPromise.resolve(item).then(res => {
pObj[index] = res
})
}
})
})
}
static allSettled(arr) {
const newArr = []
return new Promise((resolve, reject) => {
arr.forEach(item => {
item
.then(
res => {
newArr.push({ status: 'fulfilled', value: res })
},
err => {
newArr.push({ status: 'rejected', reason: err })
}
)
.finally(() => {
if (newArr.length === arr.length) {
resolve(newArr)
}
})
})
})
}
static any(arr) {
return new JcPromise((resolve, reject) => {
if (!arr.length) return reject('AggregateError: All promises were rejected')
const newArr = []
arr.forEach(item => {
item.then(
res => {
resolve(res)
},
err => {
newArr.push(err)
if (newArr.length === arr.length) {
reject('AggregateError: All promises were rejected')
}
}
)
})
})
}
static race(arr) {
return new JcPromise((resolve, reject) => {
arr.forEach(item => {
item.then(
res => {
resolve(res)
},
err => {
reject(err)
}
)
})
})
}
}