wencaizhang

await-to-js 的使用和源码分析

Published onJune 05, 2024
-Views
1Minutes Read

背景

最近发现了一个 await-to-js,它的作用就是把 promise 包装一层,方便我们处理错误。
我们知道 Promise 的出现解决了回调地狱的问题,async await 能将异步代码以同步代码的方式来书写,但是这两者在开发过程中都少不了对错误的捕捉,前者使用 Promise.prototype.catch() 后者使用 try...catch
例如:
提醒一句,在实际开发中,有些同学嫌麻烦不喜欢捕捉错误,但是我强烈建议不要这样,否则你总有一天会迟到苦头的(别问我为什么知道😭)。

什么是

我们原来写 async 函数的时候需要这样写:
其中
是一个返回 Promise 实例的函数。
使用 try...catch 捕获错误就会让核心处理逻辑放在 try 后面的代码块内部,如果逻辑代码比较长,这样看起来总是不够简洁,又或者
还需要在 try...catch 之后再次使用,那就需要把 data 的定义提升到 try...catch 之前,然后在 try 的代码块中赋值,这样的结果就是除了不够简洁之外还需要注意类型问题。
然而使用
之后,就可以不用 try...catch 包裹了,代码如下:
经过
函数处理会返回一个新的 Promise 实例,这个新 Promise 实例无论执行成功还是失败总是返回一个数组。
数组第一项用来存放 promiseFn() 的错误信息,如果没有错误信息则为
,第二项用来存放 promiseFn() 执行成功的结果,如果没有则为
然而由于 promiseFn() 最终状态要么是成功要么是失败,不可能同时是成功和失败,因此数组中要么第一项为
,要么第二项为
await-to-js
举一个形象一点的例子就是
函数返回了两把座椅,第一把座椅是
专属,如果出现
了,就让它坐下来,如果没有
就把椅子空着,第二把是
专属,原 promise 执行成功了,就让执行结果坐第二把椅子,要不就空着。
由于这个特点我们就可以根据
是否存在来判断原 promise 的执行状态,可以在 error 存在的情况下直接
提前结束函数。
那么
还需要写吗?
答案是,不用。请继续看下面的源码(源码超简单的)。

源码

为了容易理解,这里使用的是编译后的 js 代码:
没错,实际上源码就只有一个函数,分别在原 promise 的
中返回了前面说的固定结构的数组。
看了源码我们还能有额外收获,
函数可以传入第二个参数,这个参数会被合并(
)到
错误对象中,这一点是 await-to-js 文档没有提到的(大概是使用概率很低)。
有一个细节需要提一下,
中直接返回了数组,这会导致
总是成功状态,这也就是外层调用
的时候不需要使用
捕获错误的原因。
至于你本来的
执行失败已经在
内部捕获了,并且将
传递出来由调用者来决定如何处理。

探寻
返回值对 promise 状态的影响。

上面提到的细节我估计很多同学不一定注意到,那就是
回调函数的返回值决定了这个 promise 最终状态。
举个最简单的例子:
上面的
函数直接执行,最终将得到一个
状态的 Promise 实例,如果稍微改写一下:
添加一个
即使什么都不做,
也将变成
状态的的 Promise 实例。
这是因为 p2 的结果(状态)其实依赖于
回调函数的返回结果,如果回调函数返回了
或者抛出了一个错误信息,这个 promise 的状态也会变为
简而言之就是 p2 的结果(状态)由
执行链条中的最后一个决定。

源码给我的启发

看完源码,我觉得
这个结构也很适合在做一些校验的函数中作为返回值使用。
比如之前我们会在检验通过返回检验值,检验未通过的时候返回
,调用校验的时候就根据返回值是否为
来判断是否通过。
但是这样的问题就是返回值类型不确定,而且传递信息有限,比如想要返回未通过检验的原因,那就需要将
改为更复杂的结构。
如果改成
结构,我们就能根据第一项是否有值来判断是否通过校验,而且
本身就可以是未通过校验的原因,这样会更优雅。
我们以「值是否大于 10」为例来写一个校验函数:
接下来调用校验函数:
得益于
结构,我们可以直接拿到 error 进行打印或者对用户进行提示,而且校验函数的返回值结构还总是固定的。
怎么样,代码是不是简洁优雅了许多😄

Reference

Tags:
#异步
#Await-to-js
#Promise
#Async