你还在用 npm 的 star 数来选择依赖吗?在 npm 安全性问题随时爆发 的今天,作为前端开发者的我们应该具备源码阅读的能力,最好知道自己在用什么,这样在使用外部 npm
依赖时才有安全感不是么?
最近遇到一个细思极恐的问题,笔者最近老是收到防脱广告推送亦或是一些与笔者最近说出来的话相关的广告,以前只知道网上的信息流会被窃据,如今难不成语音也会监听窃取了???吓得我赶紧关掉了所有麦的权限。虽然我们不能自己造个手机,但也不能活得没有安全感。
拿 axios 这个我们常用的依赖来说,如果哪天被篡改了,那后果真不敢想象。即便这基本是不可能的,但切图仔中的精英不能就此停止追求进步的脚步。好在笔者前些天写了篇 axios 重构经验分享,多少可以证明自己努力过。 ?
这还不够,翻了下最常用的依赖,其中 es6-promise 特别惹眼 (源码比较晦涩难懂,说白了就是有点乱),本来早就想深入了解 Promise ,于是毫不犹豫决定造它。由于笔者在过渡到 TypeScript ,所以本次开发依旧会采用 TypeScript 来敲。
这应该是笔者最后一次用 TypeScript 冠名分享文章,再见 ?,我已经可以安全上路了。( 喊了那么多次,快上车,都没有多少人上车,那我就先走了。)
本文适合从零了解或者想重新深入研究 Promise 的读者,并且可以得到如下知识:
- Promise 重要知识点
- Promise API 实现方法
- 前端何来安全感
笔者希望读者可以仅通过看仅此一篇文章就可以对 Promise 有个深刻的认知,并且可以自己实现一个 Promise 类。所以会从方方面面讲 Promise,内容可能会比较多,建议读者选读。
为什么要实现 Promise
Promise 出现在 Es6 ,如果 Es5 需要使用 Promise,通常需要用到 Promise-polyfill
。也就是说,我们要实现的是一个 polyfill。实现它不仅有助于我们深入了解 Promise 而且能减少使用中犯错的概率,以至于获得 Promise 最佳实践。
从另外一个角度来说重新实现某个依赖也是一种源码解读的方式,坚持这么做,意味着解读源码能力的提升。
Promise
Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法注册了两个回调函数,用于接收 promise 的终值或本 promise 不能执行的原因。
来看笔者用心画的一张 API 结构图 ( 看不清楚的可以进我的 GitHub 看,有大图和 xmind 源文件 ):
上图只是一个 Promise 的 API 蓝图,其实 Promises/A+
规范并不设计如何创建、解决和拒绝 promise,而是专注于提供一个通用的 then 方法。所以,Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。如果大家都按规范来,那么就没有那么多兼容问题。(PS:包括 web 标准 ) 接着聊下 Promises/A+
,看过的可以跳过。
Promises/A+
所有 Promise 的实现都离不开 Promises/A+ 规范,内容不多,建议大家可以过一遍。这边讲一些规范中重要的点
术语
-
Promise
一个拥有then
方法的对象或函数,其行为符合Promises/A+
规范; -
thenable
一个定义了then
方法的对象或函数,也可视作 “拥有then
方法” -
值(value)
指任何 JavaScript 的合法值(包括 undefined , thenable 和 promise) -
异常(exception)
使用throw
语句抛出的一个值 -
据因(reason)
表示一个 promise 的拒绝原因。
Promise 的状态
一个 Promise 的当前状态 必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。
-
等待态(Pending)
处于等待态时,promise 需满足:
可以
迁移至执行态或拒绝态 -
执行态(Fulfilled)
处于执行态时,promise 需满足:
不能
迁移至其他任何状态,必须拥有一个不可变
的终值
-
拒绝态(Rejected)
处于拒绝态时,promise 需满足:
不能
迁移至其他任何状态,必须拥有一个不可变
的据因
这里的不可变指的是恒等(即可用
===
判断相等),而不是意味着更深层次的不可变( 指当 value 或 reason 不是
基本值时,只要求其引用地址相等,但属性值可被更改)。
Then 方法
一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。
promise 的 then 方法接受两个参数:
promise.then(onFulfilled, onRejected);
-
onFulfilled
和onRejected
都是可选参数。 - 如果
onFulfilled
是函数,当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值,在 promise 执行结束前其不可被调用,其调用次数不可超过一次 - 如果
onRejected
是函数,当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因,在 promise 被拒绝执行前其不可被调用,其调用次数不可超过一次 -
onFulfilled
和onRejected
只有在执行环境堆栈仅包含平台代码 ( 指的是引擎、环境以及 promise 的实施代码 )时才可被调用 - 实践中要确保
onFulfilled
和onRejected
方法异步执行,且应该在then
方法被调用的那一轮事件循环之后的新执行栈中执行。 -
onFulfilled
和onRejected
必须被作为函数调用即没有 this 值 ( 也就是说在 严格模式(strict) 中,函数 this 的值为 undefined ;在非严格模式中其为全局对象。) - then 方法可以被同一个 promise 调用多次
- then 方法必须返回一个 promise 对象
Then 参数 (函数) 返回值
希望读者可以认真看这部分的内容,对于理解 promise 的 then
方法有很大的帮助。
先来看下 promise 执行过程:
大致的过程是,promise 会从 pending
转为 fulfilled
或 rejected
,然后对应调用 then
方法参数的 onFulfilled
或 onRejected
,最终返回 promise
对象。
进一步理解,假定 有如下两个 promise:
promise2 = promise1.then(onFulfilled, onRejected);
会有以下几种情况:
- 如果
onFulfilled
或者onRejected
抛出异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
- 如果
onFulfilled
不是函数 且 promise1 成功执行, promise2 必须成功执行并返回 相同的值 - 如果
onRejected
不是函数 且 promise1 拒绝执行, promise2 必须拒绝执行并返回 相同的据因
希望进一步搞懂的,可以将下面代码拷贝到 chrome 控制台或其他可执行环境感受一下:
// 通过改变 isResolve 来切换 promise1 的状态
const isResolve = true;
const promise1 = new Promise((resolve, reject) => {
if (isResolve) {
resolv