简介
长话短说,promise 是 EcmaScript 6 提出的新特性,主要是用来解决异步调用中过度依赖回调的问题(或者说是以链式的调用的方式解决回调地狱的问题)。
// 此处不对 promise 做介绍,有需要的可以去 阮一峰老师的 ES6 教程 看看或者这本 promise迷你书 (更加完善,而且可以在线运行测试代码,荐)。
起因
做任何事都是要有起因的,不可能空穴来风,在被折腾了好几番之后,最终决定写下该文,毕竟趟完坑不能自己藏着掖着,把一点心得贡献出来也是非常值得的。
具体细节则主要和三月份对公司项目的那次重构有关。当时自己也是刚入职,面对一个文件中上千行面向过程式、MVC 混为一谈、函数名 r、j、k 式命名的代码让人痛苦不已。为了清晰的了解业务逻辑,技术实现细节以及保证最小可执行单位的稳定性,我和同事花了大半天的时间做了给一千多行的 “劣质代码” 分模块,加注释的工作。因为自己以前还算是对 proimse 颇有心得,就果断使用 promise 处理工作流程了,没想到还是跌到坑里了,不过就目前来讲,都是非常值得的事情。
使用 promise 实现对数据的分层处理
因为 promise 主要使用场景是异步任务,就不用同步任务抬杠了。此处我们主要以请求数据为例展开做介绍。(请求库可以是 request、 superagent、fetch、AJAX 亦或是 jQuery,都没关系)
首先,我们需要在数据返回的地方将请求返回的结果按照 API 所给的回调参数(如 error 对象是否为空,body 是否存在、亦或是从 reponse 中利用 statusCode 来判断,不过这个一般请求库都做好了,除非你有对不同的异常需要特殊处理)分流,通过在对应的地方调用 resolve 或 reject 来构造 promise 对象。
那么 promise 对象有了,接下来该怎么办呢?有如下场景,想要把对应的状态 (resolve 或 reject) 想要传递下去,或者说想要把带有同样信息甚至是处理过的信息传递到下一层 then 中 (只要返回该 promise 即可),就一件事,保留状态 + 传递被处理后的数据。
如下代码:// 此处拿通过 promise 处理过的 superagent 举例(处理函数为写在原型上的 promisify)
request.get("your api")
.promisefy()
.then(
res => res.text,
error => Promise.reject(error.text)
)
如上代码主要体现了
- 传递处理后的数据,如这里我们向下级 then 的 resolve、reject 传递了
res.text
,同样还有error.text
// 其实这两个都是被 superagent 处理后两种状态的 response。 - 对于在 reject 中使用
Promise.reject(error.text)
则表现了对状态的保存,譬如当 reject 事件发生后,能够通过返回一个新的 promise (这里仅有 reject 的一个 promise 对象)来连接下一个 then. 其实准确来讲,第一个也应该是res => Promise.resolve(res.text)
, 不过鉴于在 promise 机制中,当前方 then 中被执行的回调返回了一个非 promise 对象时,当前 then 的 resolve 回调的参数则会是上一个被执行回调的返回值(不论是 resolve 还是 reject 被执行)。
- 这里我提供一点测试代码
const promiseTest = input => new Promise(resolve, reject) { |
使用 race 来给 fetch 实现一个伪超时机制
众所周知,promise 拥有两个最实用的静态方法 —— race
和 all
,二者都接受一个以 promise 数组的参数。
- race: 通过让多个 promise 以竞争的方式来让第一个完成 promise 的结果出现在 then 中。
- all:当多个异步任务只有在全部完成时才会进行下一步操作时,就需要它了,它表示让所有 promise 执行成功且完毕才会被 resolve,而其参数则是每一个 promise resolve 结果的数组集,而 reject 时则是对应发生错误 promise 的结果集。
通过以上两个特性,我们就可以通过 race 结合 fetch 以及 setTimeout
来做一个简单的超时机制。// 使用 setTimeout 封装的 promise
const delay = ms => new Promise(resolve, reject) {
setTimeout(() => {
reject(new Error("timeout"))
}, ms)
}
const fetchWithTimeout = (ms, ...args) => {
return Promise.race([fetch(...args), delay(ms)])
}
这样将正常的 fetch 请求和一个 timer 事件进行 race,最先完成的既是运行结果。不过需要注意的一点是:
fetch 请求并不会因此而被终止掉,而是会继续请求