哈Ha! 我向您介绍了杰克·波迪(Jack Pordi)的文章
“ JavaScript异步/等待和承诺:像您五岁一样解释”的翻译。
在某些时候,任何认为自己是JavaScript开发人员的人都应该遇到回调函数,promise,或者最近才看到async / await语法。 如果您在游戏中玩了足够长的时间,您可能会遇到嵌套回调函数是实现JavaScript异步的唯一方法的时代。
当我开始用JavaScript学习和写作时,已经有十亿个教程和教程说明如何在JavaScript中实现异步。 但是,它们中的许多内容仅说明了如何将回调函数转换为async / await中的promise或promises。 对于许多人来说,这可能足以与他们相处并开始在其代码中使用它们。
但是,如果像我一样,您真的想了解异步编程(而不仅仅是JavaScript语法!),那么您可能会同意我的观点,即从头开始解释异步编程的材料不足。
异步是什么意思?

通常,问这个问题,您可以从以下内容中听到一些信息:
- 有多个线程可以同时执行代码。
- 一次执行多个代码。
- 这是并发的。
在某种程度上,所有选项都是正确的。 但是,除了给您一个可能很快会忘记的技术定义之外,我将举
一个甚至一个孩子都能理解的例子 。
生活例子

想象你正在煮蔬菜汤。 为了简单和良好地进行类比,假设蔬菜汤仅包含洋葱和胡萝卜。 这种汤的配方可能如下:
- 切碎胡萝卜。
- 切碎洋葱。
- 将水倒入锅中,打开火炉,直到沸腾。
- 将胡萝卜加入锅中,静置5分钟。
- 将洋葱加入锅中,再煮10分钟。
这些说明是简单易懂的,但是如果你们中的一个人读了这篇,真的知道如何做饭,您可以说这不是最有效的烹饪方法。 而且您会说对的,这就是为什么:
- 实际上,步骤3、4和5 并不需要您作为厨师来做任何事情,只是要观察过程并跟踪时间。
- 步骤1和2 需要您积极地做某事。
因此,经验丰富的厨师的食谱可能如下:
- 开始煮一锅水。
- 在等待锅煮沸的同时,开始切胡萝卜。
- 等胡萝卜切碎时,水会沸腾,所以加入胡萝卜。
- 将胡萝卜放在锅中煮熟时,切碎洋葱。
- 加入洋葱,再煮10分钟。
尽管所有动作都保持不变,但您有权期望此选项将更快,更高效。 这正是异步编程的原理:
您永远不想坐下来,只等什么,而可以将时间花在其他一些有用的事情上。
我们都知道,在编程中,
等待某事的频率非常高-无论是等待服务器的HTTP响应还是用户的动作或其他原因。 但是,处理器的执行周期非常宝贵,应该
始终积极地使用它们,做一些事情,并且不要期望:这会导致
异步编程 。
现在开始使用JavaScript,好吗?
因此,按照相同的蔬菜汤示例,我将编写一些函数来表示上述食谱的步骤。
首先,让我们编写表示不需要时间的任务的同步函数。 这些是很好的旧JavaScript函数,但是请注意,我已经将
chopCarrots
和
chopOnions
描述为需要活跃工作(和时间)的任务,从而使它们可以进行较长的计算。 完整的代码位于文章[1]的末尾。
function chopCarrots() { console.log(" !"); } function chopOnions() { console.log(" !"); } function addOnions() { console.log(" !"); } function addCarrots() { console.log(" !"); }
在继续使用异步功能之前,我将首先快速解释一下JavaScript类型系统如何处理异步:基本上
,异步操作的所有结果(包括错误)都应包装在promise中 。
为了使函数返回承诺,您可以:
- 明确地兑现诺言,即
return new Promise(…)
; - 隐式返回Promise-将
async
添加到函数声明中,即 async function foo()
; - 使用这两个选项 。
有一篇很棒的文章[2],它讨论了异步函数和返回promise的函数之间的区别。 因此,在我的文章中,我将不讨论该主题(要记住的主要事情):您应该
始终在异步函数中使用
async
。
因此,我们的异步功能代表制备蔬菜汤的步骤3-5,如下所示:
async function letPotKeepBoiling(time) { return;
我再次删除了实现细节,以免被它们分散注意力,但是它们在文章[1]的末尾发布。
重要的是要知道,为了等待promise的结果,以便以后可以使用它做点什么,您可以简单地使用
await
关键字:
async function asyncFunction() { } result = await asyncFunction();
因此,现在我们只需要将所有内容放在一起:
function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log(" !"); } makeSoup();
但是等等! 这行不通! 您将看到
SyntaxError: await is only valid in async functions
错误中
SyntaxError: await is only valid in async functions
。 怎么了 因为如果您不使用
async
声明函数,则默认情况下JavaScript将其定义为同步函数-同步意味着无需等待! [3]。 这也意味着您不能在函数外部使用
await
。
因此,我们只需将
async
添加到
makeSoup
函数中:
async function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log(" !"); } makeSoup();
瞧! 请注意,在第二行中,我调用了不带
await
关键字的异步函数
boilPot
,因为我们不想在开始切胡萝卜之前等待平底锅煮沸。 在将胡萝卜放入
pot
之前,我们只期望在第五行有
pot
的承诺,因为我们不想在水沸腾之前这样做。
await
呼叫期间会发生什么? 好吧,什么都没有...
在
makeSoup
函数的上下文中
makeSoup
您可以简单地将其视为期望发生的事情(或最终将返回的结果)。
但是请记住:
您(像您的处理器一样)永远不会只是坐在那里等东西,而可以将时间花在其他事情上 。
因此,我们不仅可以煮汤,还可以并行煮其他东西:
makeSoup(); makePasta();
例如,当我们等待
letPotKeepBoiling
,我们可以煮意大利面。
看吗 async / await语法实际上非常易于使用,如果您理解的话,同意吗?
公开承诺呢?
好吧,如果您坚持要说的话,我将转向使用显式promise(
大约Transl 。:通过显式promise,作者直接隐含promise的语法,并通过隐式promise异步/ await的语法,因为它隐式地返回了promise-无需编写return new Promise(…)
)。 请记住,异步/等待方法
基于promise本身,因此这两个选项是完全兼容的 。
在我看来,显式承诺介于旧式回调和新的异步/等待性语法之间。 另外,您也可以将异步/等待性语法视为隐式承诺。 最后,async / await构造在promise之后出现,而promise在回调函数之后出现。
使用我们的时间机器移至回调地狱[4]:
function callbackHell() { boilPot( () => { addCarrots(); letPotKeepBoiling(() => { addOnions(); letPotKeepBoiling(() => { console.log(" !"); }, 1000); }, 5000); }, 5000, chopCarrots(), chopOnions() ); }
我不会撒谎,在撰写本文时,我即时编写了这个示例,这花了我很多时间而不是我想承认的时间。 你们中的许多人甚至可能都不知道这里发生了什么。
我亲爱的朋友,所有这些回调函数不是很糟糕吗? 让我们永远不要再使用回调函数是一个教训...并且,如所承诺的,具有显式promise的相同示例:
function makeSoup() { return Promise.all([ new Promise((reject, resolve) => { chopCarrots(); chopOnions(); resolve(); }), boilPot() ]) .then(() => { addCarrots(); return letPotKeepBoiling(5); }) .then(() => { addOnions(); return letPotKeepBoiling(10); }) .then(() => { console.log(" !"); }); }
如您所见,promise仍然类似于回调函数。
我不会详细介绍,但最重要的是:
.then
是一个promise方法,它获取结果并将其传递给参数函数(本质上是传递给回调函数...)- 您永远不能在
.then
上下文之外使用承诺的结果。 本质上,.then就像一个异步块,它期待结果,然后将其传递给回调函数。 - 除了
.catch
方法之外, .catch
还有另一种方法.catch
。 需要处理承诺中的错误。 但是我不会详细介绍,因为已经有十亿篇有关该主题的文章和教程。
结论
我希望您能从本文中获得对Promise和异步编程的了解,或者至少可以从生活中学到一个很好的例子来向其他人解释。
那么,您使用哪种方式:promise或async / await?答案完全取决于您-我要说的是,将它们结合起来并不是那么糟糕,因为两种方法彼此完全兼容。
但是,我个人100%处于异步/等待阵营,因为对我而言,代码更易于理解,并且更好地反映了异步编程的真正多任务处理能力。
[1] :完整的源代码
在这里 。
[2] :原始文章
“异步功能与 返回Promise的函数“ ,翻译
”异步函数与返回许诺的函数之间的区别“。[3] :您可能会说JavaScript可能可以从函数体中确定异步/等待类型并进行递归检查,但是JavaScript并非旨在在编译时照顾静态类型的安全,更不用说了对于开发人员来说,显式查看功能类型要方便得多。
[4] :我编写了“异步”函数,假设它们在与
setTimeout
相同的接口下工作。 注意,回调与promise不兼容,反之亦然。