学会通过规范解决问题

一位朋友问了我一个问题,promise.then的第二个参数和.catch有什么区别?怎么说也用了2年的Promise,也应该能“精通”使用Promise API了啊!但是却一时半会没法回答出有什么区别,于是我又重新深入的认识了下Promise。第一手资料则是Promise/A+的规范,大多数实现Promise的库也是按照A+来实现的比如Q.js PS:不想看规范的直接略过看最下面结论。

1.术语

1.1 "Promise"是一个带有then方法的对象或方法,它的行为必须符合本规范

1.2 "thenable"是一个有then方法的对象或方法

1.3 "value"是合法Javascript的值(包括undefined,thenable,Promise)

1.4 "exception" 是一个被throw抛出的值

1.5 "reason" 是一个值表明了promise为什么拒绝

2.要求

2.1 Promise 状态

Promise必须是这三种状态的其中之一:pending,fulfilled,rejected

2.1.1 当pending时

2.1.1.1 状态可能会变换成 fulfilled或rejected

2.1.2 当fulfilled时

2.1.2.1 不能切换为别的状态

2.1.2.2 必须有一个值且该值不允许改变

2.1.3 当rejected时

2.1.3.1 不能切换为别的状态

2.1.3.2 必须有拒绝的原因且不允许改变

在这里,“不允许改变” 意味着它是不可变唯一的(即 ===),但并不是说它是深度不可变

2.2 then方法

Promise 必须提供then方法,当前(最后)通过它可以得到value(reason)

// Promise的then方法接受两个参数
promise.then(onFulfilled, onRejected)

2.2.1 onFulfilled 和 onRejected 都是可选的参数

2.2.1.1 如果 onFulfilled 不是函数,它会被忽略

2.2.1.2 如果 onRejected 不是函数,它会被忽略

2.2.2 如果 onFulfilled 是一个函数

2.2.2.1 当promise为fulfilled后会被调用,切promise的value会作为它的第一个参数

2.2.2.2 不能在promise为fulfilled之前调用

2.2.2.3 只允许被调用一次

2.2.3 如果 onRejected 是一个函数

2.2.3.1 当promise为rejected后会被调用,且promise的reason会作为它的第一个参数

2.2.3.2 不能在promise为rejected之前调用

2.2.3.3 只允许被调用一次

2.2.4 onFulfilled 或 onRejected 直到执行上下文调用栈仅有平台代码的时候才能被调用

2.2.5 onFulfilled 和 onRejected 必须作为函数调用

2.2.6 then在同一个promise中可以被多次调用

2.2.6.1 当 promise 是 fulfilled, 所有的 onFulfilled 回调 会按照顺序执行即then调用的顺序

2.2.6.2 当 promise 是 rejected, 所有的 onRejected 回调 会按照顺序执行即then调用的顺序

2.2.7 then必须返回promise

promise2 = promise1.then(onFulfilled, onRejected);

2.2.7.1 任何一个 onFulfilled 或 onRejected 返回了一个value x, 执行Promise决议过程 [[Resolve]](promise2, x)

2.2.7.2 任何一个 onFulfilled 或 onRejected 抛出异常ee会作为reason被 promise2 拒绝

2.2.7.3 如果 onFulfilled 不是函数且 promise1 已 fulfilled, promise2也会fulfilled且value和promise1的value一样

2.2.7.4 如果 onRejected 不是函数且 promise1 已 rejected, promise2也会 rejected且reason和promise1的reason一样

2.3 Promise决议过程

Promise决议过程是讲一个promise和一个value作为输入的抽象操作,我们可以用[[Resolve]](promise, x)来表示。如果x是可以then的或x的行为看起来像一个promise,它会尝试用promise去接收x的状态,否则会用x的作为value来fulfilled promise。

只要暴露了兼容Promise/A+-的then,不同的promise是可以相互处理操作的。它允许Promise/A+去"同化"不一样的实现,只要有合适的then方法

[[Resolve]](promise, x), 会执行如下几个步骤:

2.3.1 如果 promise 和 x 引用了同一个对象, 用TypeError作为reason去reject promise

2.3.2 如果x是一个promise,采取它的状态

2.3.2.1 如果x是pending,promise会保持pending直到x是fulfilled或rejected

2.3.2.2 如果x是fulfilled,fulfill promise用同一个value

2.3.2.3 如果x是rejected,reject promise用同一个reason

2.3.3 如果x是一个对象或者方法

2.3.3.1 使then为x.then 

2.3.3.2 尝试去获取x.then的结果,如果抛出了e,则作为reason reject promise

2.3.3.3 如果then是函数,将x作为this调用,第一个参数是resolvePromise,第二个参数是rejectPromise

    2.3.3.3.1 如果 resolvePromise 调用后返回value y,则run `[[Resolve]](promise, y)`

    2.3.3.3.2 如果 rejectPromise 返回了reason r,则r作为reason reject promise 

    2.3.3.3.3 如果 resolvePromise 和 rejectPromise同时被调用,或者被同一个参数调用了多次,则取第一个,其他的将会被忽略

    2.3.3.3.4 如果then抛出错误e

        2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用过了,则忽略

        2.3.3.3.4.2 否则e作为reason reject promise

2.3.3.4 如果then不是函数,则x作为value fulfill promise

2.3.4 如果x不是对象也不是函数,则x作为value fulfill promise

结论

其实即使看完了A+,我们也没法回答刚刚的问题,但是我们对如何实现一个Promise,Promise三种状态是如何进行决策是有了更清晰的认识。那我们继续寻找问题的答案。从控制台log会发现,浏览器的确实现了更多的方法,race,all,原型上有catch。那具体应该如何实现呢? 此时我们就可以从MDN里找到ES2015的Promise规范。

promise.prototype.catch在这里我们就可以明确的得知,原来catch只是then(void 0, onRejected)的封装罢了。让我们再看看A+中的点2.2.7,then必须返回一个Promise。

所以他们最大的区别就是 then第二个参数是直接reject,而catch是返回了一个新的promise再reject

最后

学会如何查找规范,如何看规范是前端的必经之路啊!