持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
JavaScript中的async/await功能的效用是基于这样的想法:异步代码很难,相比之下,同步代码更容易。这在客观上是正确的,但在大多数情况下,我不认为async/await真的能解决这个问题。
谎言和async/await
我用来确定是否要使用某个模式的指标之一是它所带来的代码综合质量。例如,一个模式可能是干净的、简洁的或广泛使用的,但如果它导致了容易出错的代码,它就是一个我可能会拒绝的模式。这些模式是双刃剑,很容易搬起石头砸自己的脚。
首先,它是建立在一个谎言之上的。
Async/await让你的异步代码看起来像同步的一样。
这是它的卖点。但对我来说,这就是问题所在。它从一开始就为你的代码所发生的事情设定了错误的心理模型。同步代码可能比异步代码更容易处理,但同步代码不是异步代码。它们有非常不同的属性。
很多时候这不是问题,但当它是问题时,就很难识别,因为async/await正好隐藏了显示它的线索。以这段代码为例。
同步代码
const processData = ({ userData, sessionPrefences }) => {
save('userData', userData);
save('session', sessionPrefences);
return { userData, sessionPrefences }
}
复制代码
Async/Await
const processData = async ({ userData, sessionPrefences }) => {
await save('userData', userData);
await save('session', sessionPrefences);
return { userData, sessionPrefences }
}
复制代码
Promise
const processData = ({ userData, sessionPrefences }) => save('userData', userData)
.then(() => save('session', sessionPrefences))
.then(() => ({ userData, sessionPrefences })
复制代码
这里有一些性能问题。我们已经把问题缩小到了processData函数上。在这三种情况中,你对优化途径的假设是什么?
我看了第一种情况,发现我们在两个不同的地方保存了两块不同的数据,然后只是返回一个对象。唯一可以优化的地方是保存函数。没有任何其他选择。
我看了第二个例子,也有同样的想法。唯一可以优化的地方是保存函数。
也许只是因为我对Promise的太熟悉了,但我看了第三个例子,我很快看到了一个机会。我看到我们在连续调用save,尽管其中一个并不依赖于另一个。 我们可以将我们的两个save调用并行化。
const processData = ({ userData, sessionPrefences }) => Promise.all([
save('userData', userData),
save('session', sessionPrefences)
])
.then(() => ({ userData, sessionPrefences })
复制代码
同样的机会也存在于async/await代码中,只是因为我们处于异步代码的思维模式中,所以它被隐藏在明处。在async/await版本中并不是没有提示。关键字async和await应该给我们同样的直觉,就像第三个版本中的then一样。但我敢打赌,对许多工程师来说,它并没有。
为什么没有呢?
这是因为我们被教导要以同步的思维方式来阅读async/await代码。在第一个同步代码例子中,我们无法将保存调用并行化,同样的逻辑(但现在是不正确的),我们来到第二个例子。Async/await将我们的思维置于同步的思维模式中,而这是错误的思维模式。
此外,如果我们要在async/await的例子中利用并行化的优势,无论如何我们必须使用promise。