ES2017 于 6 月正式发布,随之而来的是对我最喜欢的 JavaScript 特性之一 async
函数的广泛支持!如果您曾经在理解异步 JavaScript 方面遇到困难,那么这篇文章适合您。如果您没有遇到过,那么,好吧,您可能是一位超级天才。
异步函数或多或少地允许您编写顺序的 JavaScript 代码,而无需将所有逻辑都包装在回调、生成器或 Promise 中。 请考虑以下代码
function logger() {
let data = fetch('http://sampleapi.com/posts')
console.log(data)
}
logger()
此代码无法按预期执行。如果您使用 JS 构建过任何东西,您可能知道原因。
但是这段代码确实按预期执行。
async function logger() {
let data = await fetch('http://sampleapi.com/posts')
console.log(data)
}
logger()
这段直观(且漂亮)的代码可以工作,并且它只增加了两个词!
ES6 之前的异步 JavaScript
在深入探讨 async
和 await
之前,您必须了解 Promise。为了更好地理解 Promise,我们需要再退一步,回到简单的回调函数。
Promise 在 ES6 中引入,极大地改善了在 JavaScript 中编写异步代码的方式。不再有“回调地狱”,有时人们会这样亲切地称呼它。
回调函数是可以传递给函数并在该函数内调用的函数,作为对任何事件的响应。它是 JS 的基础。
function readFile('file.txt', (data) => {
// This is inside the callback function
console.log(data)
}
该函数只是从文件中记录数据,只有在文件读取完成后才能执行。这似乎很简单,但是如果您想按顺序读取和记录五个不同的文件怎么办?
在 Promise 出现之前,为了执行顺序任务,您需要嵌套回调函数,如下所示
// This is officially callback hell
function combineFiles(file1, file2, file3, printFileCallBack) {
let newFileText = ''
readFile(string1, (text) => {
newFileText += text
readFile(string2, (text) => {
newFileText += text
readFile(string3, (text) => {
newFileText += text
printFileCallBack(newFileText)
}
}
}
}
这很难理解和难以跟踪。这甚至不包括其中一个文件不存在的完全可能情况下的错误处理。
我保证会更好(明白了吗?!)
这就是 Promise
可以提供帮助的地方。Promise 是一种推理尚不存在但您知道将会存在的数据的方式。Kyle Simpson 是《你不知道 JS 系列》的作者,以发表关于异步 JavaScript 的演讲而闻名。 他在这次演讲中对 Promise 的解释非常到位:就像在快餐店点餐一样。
- 点餐。
- 支付餐费并收到带有订单号的票据。
- 等待食物。
- 食物准备好了,他们会喊您的票号。
- 取餐。
正如他指出的那样,您可能无法在等待食物时就吃掉它,但您可以考虑它,并且可以为此做好准备。您可以继续您的一天,因为您知道食物会送达,即使您还没有收到它,因为食物已经“承诺”给您了。这就是 Promise 的全部含义。一个表示最终会存在的数据的对象。
readFile(file1)
.then((file1-data) => { /* do something */ })
.then((previous-promise-data) => { /* do the next thing */ })
.catch( /* handle errors */ )
这就是 promise
语法。它的主要优点是它允许以一种直观的方式将顺序事件链接在一起。这个基本示例还可以,但是您可以看到我们仍在使用回调函数。Promise 只是回调函数的薄包装,使它更直观。
最佳方法(新):Async/Await
几年前,异步函数进入了 JavaScript 生态系统。截至上个月,它已成为该语言的正式特性,并得到广泛支持。
async
和 await
关键字是构建在 Promise 和生成器之上的薄包装。从本质上讲,它允许我们在任何我们想要的地方使用 await
关键字“暂停”我们的函数。
async function logger() {
// pause until fetch returns
let data = await fetch('http://sampleapi.com/posts')
console.log(data)
}
此代码运行并按预期执行。它记录了 API 调用的数据。如果您的大脑没有爆炸,我不知道如何让您满意。
这样做的好处是它很直观。您可以按照大脑思考的方式编写代码,告诉脚本在需要的地方暂停。
另一个优点是,我们可以以 Promise 无法实现的方式使用 try
和 catch
async function logger () {
try {
let user_id = await fetch('/api/users/username')
let posts = await fetch('/api/`${user_id}`')
let object = JSON.parse(user.posts.toString())
console.log(posts)
} catch (error) {
console.error('Error:', error)
}
}
这是一个人为的例子,但它证明了一个观点:catch
将捕获在该过程中任何步骤中发生的错误。try
块至少有 3 个地方可能失败,这使得它成为处理异步代码中错误的最简洁的方法。
我们还可以毫不费力地在循环和条件语句中使用异步函数
async function count() {
let counter = 1
for (let i = 0; i < 100; i++) {
counter += 1
console.log(counter)
await sleep(1000)
}
}
这是一个愚蠢的例子,但它将按预期运行,并且易于阅读。如果您在控制台中运行此代码,您将看到代码将在 sleep 调用上暂停,并且下一个循环迭代将在一秒钟后开始。
细节
现在您已经相信了 async 和 await 的强大功能,让我们深入了解细节。
async
和await
是建立在 Promise 之上的。使用async
的函数本身始终会返回一个 Promise。这一点很重要,并且可能是您会遇到的最大的“陷阱”。- 当我们使用
await
时,它会暂停函数,而不是整个代码。 async
和await
是非阻塞的。- 您仍然可以使用
Promise
帮助程序,例如Promise.all()
。这是我们之前的示例async function logPosts () { try { let user_id = await fetch('/api/users/username') let post_ids = await fetch('/api/posts/<code>${user_id}') let promises = post_ids.map(post_id => { return fetch('/api/posts/${post_id}') } let posts = await Promise.all(promises) console.log(posts) } catch (error) { console.error('Error:', error) } }
- Await 只能在已声明为 Async 的函数中使用。
- 因此,您不能在全局作用域中使用
await
。// throws an error function logger (callBack) { console.log(await callBack) } // works! async function logger () { console.log(await callBack) }
现已可用!
截至 2017 年 6 月,async
和 await
关键字几乎可以在所有浏览器中使用。更好的是,为了确保您的代码在任何地方都能正常工作,请使用 Babel 将您的 JavaScript 预处理成旧语法,以便旧版浏览器也能支持。
如果您想了解更多 ES2017 提供的功能,可以 在此处查看 ES2017 功能的完整列表。
JavaScript 语法将与 C# 一样!这很酷!特别是对于 .NET 开发人员 :)
查看上面的代码清单:将
let
替换为var
(这确实是有效的 JS 和 C# 关键字),并将function
替换为Task
(或Task<ReturnType>
)。这就是 C# 代码!真是个宝贝。
Await 不是将异步调用变成同步调用了吗?
不,它不是。这正是您必须仅在异步函数中使用
await
的原因。有点像,但它以非阻塞的方式执行。这样做的好处是,在等待时其他异步函数仍然可以运行。例如,如果您在一个函数的堆栈中等待 sleep 函数,则事件仍然可以从代码的不同部分触发。
以下代码应该打印“main start”,然后打印“interrupting the middle of main”,最后打印“main end”。
因此,从本质上讲,async/await 是一种使单个任务同步而不会阻塞 JavaScript 事件循环的方法;
这就是要点。
唯一的警告是它是一个 Promise 包装器,因此您必须期望它返回一个 Promise。90% 的时间它不是问题,但如果您没有预料到它,它可能会导致麻烦。
不。它看起来像是异步的。
await
基本上引入了语法糖。例如可以“转译”为
简而言之,一个
async
函数总是**返回一个Promise
**,而await
是获取已解析值(以及通常用于拒绝值的try...catch
)的语法。我猜这个睡眠函数看起来会像这样。
它可以简化为
编辑:漏了一个括号
不错的文章,但我不喜欢缺少的分号:D
好文章 - 也许可以强调一下**处理拒绝的 Promise**的部分
这是一个改变游戏规则的东西:0
不,我不是
我有一个想法,如果你把所有异步任务都想象成从亚马逊订购东西:你购买一样东西,但你不会就此停止,你可以在此期间做其他事情。
值得一提的是,顶级
await
不可用。我的意思是,目前,你不能打开控制台并输入await fetch("https/css-tricks.com")
,你必须将其包装在一个async
IIFE 中。关于允许它存在讨论,但背后存在大量问题。
Chrome 的最新开发版本中存在顶级
await
:https://developers.google.com/web/updates/2017/08/devtools-release-notes#await这意味着我可以这样做吗
logPosts().then( results => mylog(results))
E
您描述的是 Promise API,正如帖子中所述,它已经可用了一段时间了。