朋友们,美好的一天!相对而言,新增的JavaScript是异步函数和await关键字。这些功能基本上是对Promise(承诺)的语法补充,使编写和读取异步代码变得容易。它们使异步代码看起来像同步的。本文将帮助您弄清楚是什么。条件:基本的计算机知识,JS基础知识,理解异步代码和Promise的基础。目的:了解如何作出承诺以及如何使用它们。异步/等待基础
使用异步/等待有两个部分。异步关键字
首先,我们有async关键字,该关键字放在函数声明之前以使其异步。异步功能是一种预期可以使用await关键字运行异步代码的功能。尝试在浏览器控制台中键入以下内容:function hello(){ return 'Hello' }
hello()
该函数将返回“ Hello”。没什么特别的,对吧?但是,如果我们将其转换为异步函数呢?请尝试以下操作:async function hello(){ return 'Hello' }
hello()
现在,函数调用将返回一个Promise。这是异步函数的功能之一-它们返回保证被转换为Promise的值。您还可以创建异步函数表达式,如下所示:let hello = async function(){ return hello() }
hello()
您还可以使用箭头功能:let hello = async () => { return 'Hello' }
所有这些功能都做同样的事情。为了获得完成的承诺的价值,我们可以使用.then()块:hello().then((value) => console.log(value))
...甚至是这样:hello().then(console.log)
因此,添加async关键字会使函数返回promise而不是值。另外,它允许同步功能避免与运行和支持使用await相关的任何开销。在功能之前简单添加异步功能,即可通过JS引擎自动优化代码。凉!等待关键字
当您将异步功能与await关键字结合使用时,异步功能的优势变得更加明显。可以在任何基于promise的函数之前添加它,以使其等待promise完成,然后返回结果。之后,将执行下一个代码块。您可以在调用任何返回承诺的函数(包括Web API函数)时使用await。这是一个简单的示例:async function hello(){
return greeting = await Promise.resolve('Hello')
}
hello().then(alert)
当然,上面的代码是没有用的,它仅用作语法的演示。让我们继续看一个真实的例子。使用异步/等待在Promise上重写代码
以上一篇文章的获取示例为例:fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message)
})
您应该已经了解了什么是Promise以及它们如何工作,但是让我们使用async / await重写此代码,看看它有多简单:async function myFetch(){
let response = await fetch('coffee.jpg')
let myBlob = await response.blob()
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
}
myFetch().catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message)
})
这使代码更加简单易懂-没有.then()块!使用async关键字将功能转换为一个承诺,因此我们可以使用从承诺到等待的混合方法,在单独的块中突出显示该功能的第二部分,以提高灵活性:async function myFetch(){
let response = await fetch('coffee.jpg')
return await response.blob()
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob)
let image = document.createElement('image')
image.src = objectURL
document.body.appendChild(image)
}).catch(e => console.log(e))
您可以重写示例或运行我们的实时演示(另请参见源代码)。但是它是如何工作的呢?
我们将代码包装在函数中,并在function关键字之前添加了async关键字。您需要创建一个异步函数来确定将在其中运行异步代码的代码块。 await仅在异步函数中起作用。再一次:await仅在异步函数中起作用。在myFetch()函数内部,代码非常类似于promise上的版本,但是有一些区别。不必在每个基于promise的方法之后使用.then()块,而只需在调用该方法之前添加await关键字并将一个值分配给该变量。关键字await使JS引擎在给定的行上暂停代码执行,从而允许另一个代码执行,直到异步函数返回结果为止。一旦执行,代码将从下一行继续执行。例如:let response = await fetch('coffee.jpg')
当给定值可用时,将fetch()承诺返回的值分配给响应变量,并且解析器将在该行停止,直到承诺完成为止。一旦该值变为可用,解析器便移至创建Blob的下一行代码。该行还调用了基于Promise的异步方法,因此在这里我们也使用await。当操作结果返回时,我们从myFetch()函数返回它。这意味着,当我们调用myFetch()函数时,它将返回一个promise,因此我们可以在其中添加.then(),以在其中处理屏幕上图像的显示。您可能会认为“太好了!”而且您是对的-较少用于包装代码的.then()块,它们看起来都像是同步代码,因此很直观。添加错误处理
如果要添加错误处理,则有几种选择。您可以将同步try ... catch结构与async / await一起使用。此示例是上面代码的扩展版本:async function myFetch(){
try{
let response = await fetch('coffee.jpg')
let myBlob = await response.blob()
let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
} catch(e){
console.log(e)
}
}
myFetch()
catch(){}块接受一个错误对象,我们将其命名为“ e”;现在我们可以将其输出到控制台,这将使我们能够接收到有关代码中错误发生位置的消息。如果要使用上面显示的代码的第二版,则应继续使用混合方法,并在.then()调用的末尾添加.catch()块,如下所示:async function myFetch(){
let response = await fecth('coffee.jpg')
return await response.blob()
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)
}).catch(e => console.log(e))
这是可能的,因为.catch()块将捕获异步函数和promise链中都发生的错误。如果在此处使用try / catch块,将无法处理调用myFetch()函数时发生的错误。您可以在GitHub上找到两个示例:simple-fetch-async-await-try-catch.html(请参阅源代码)simple-fetch-async-await-promise-catch.html(请参见源代码)等待Promise.all()
异步/等待基于承诺,因此您可以充分利用后者。这些特别包括Promise.all()-您可以轻松地向Promise.all()添加await以类似于同步代码的方式写入所有返回值。同样,以上一篇文章为例。保持打开的标签页与下面显示的代码进行比较。使用async / await(请参见实时演示和源代码),它看起来像这样:async function fetchAndDecode(url, type){
let repsonse = await fetch(url)
let content
if(type === 'blob'){
content = await response.blob()
} else if(type === 'text'){
content = await response.text()
}
return content
}
async function displayContent(){
let coffee = fetchAndDecode('coffee.jpg', 'blob')
let tea = fetchAndDecode('tea.jpg', 'blob')
let description = fetchAndDecode('description.txt', 'text')
let values = await Promise.all([coffee, tea, description])
let objectURL1 = URL.createObjectURL(values[0])
let objectURL2 = URL.createObjectURL(values[1])
let descText = values[2]
let image1 = document.createElement('img')
let image2 = document.createElement('img')
image1.src = objectURL1
image2.src = objectURL2
document.body.appendChild(image1)
document.body.appendChild(image2)
let para = document.createElement('p')
para.textContent = descText
document.body.appendChild(para)
}
displayContent()
.catch(e => console.log(e))
我们很容易通过一些更改使fetchAndDecode()函数异步。注意这一行:let values = await Promise.all([coffee, tea, description])
使用await,我们以类似于同步代码的方式获得变量值中三个promise的结果。我们必须将整个函数包装在一个新的异步函数displayContent()中。我们没有实现强大的代码缩减,但是能够从.then()块中提取大多数代码,这提供了有用的简化,并使代码更具可读性。为了处理错误,我们在对displayContent()的调用中添加了.catch()块;它处理两个函数的错误。请记住:您还可以使用.finally()块获取有关该操作的报告-您可以在我们的实时演示中看到它的运行情况(另请参见源代码)。异步/等待劣势
异步/等待有两个缺陷。异步/等待使代码看起来像同步的,从某种意义上讲,使其行为更加同步。与同步操作中发生的情况一样,关键字await阻止其后的代码执行,直到承诺完成为止。这允许您执行其他任务,但是您自己的代码已锁定。这意味着您的代码可能会因大量的未决承诺而被放慢速度。每次等待都将等待上一个的完成,而我们希望同时实现承诺,就好像我们没有使用异步/等待一样。有一种设计模式可以缓解此问题-通过将Promise对象存储在变量中然后等待它们来禁用所有promise过程。让我们看看这是如何实现的。我们有两个示例可供使用:slow-async-await.html(请参阅源代码)和fast-async-await.html(请参见源代码)。这两个示例均以promise函数开头,该函数使用setTimeout()模仿异步操作:function timeoutPromise(interval){
return new Promise((resolve, reject) => {
setTimeout(function(){
resolve('done')
}, interval)
})
}
然后是异步函数timeTest(),该函数希望对timeoutPromise()进行三个调用:async function timeTest(){
...
}
对timeTest()的三个调用中的每一个都以记录完成承诺的时间结束,然后记录完成整个操作所花费的时间:let startTime = Date.now()
timeTest().then(() => {
let finishTime = Date.now()
let timeTaken = finishTime - startTime
alert('Time taken in milliseconds: ' + timeTaken)
})
在每种情况下,timeTest()函数都不同。在slow-async-await.html中,timeTest()如下所示:async function timeTest(){
await timeoutPromise(3000)
await timeoutPromise(3000)
await timeoutPromise(3000)
}
在这里,我们只希望对timeoutPromise进行三个调用,每次设置3秒的延迟。每个调用都等待上一个调用的完成-如果运行第一个示例,则将在9秒钟左右看到一个模态窗口。在fast-async-await.html中,timeTest()如下所示:async function timeTest(){
const timeoutPromise1 = timeoutPromise(3000)
const timeoutPromise2 = timeoutPromise(3000)
const timeoutPromise3 = timeoutPromise(3000)
await timeoutPromise1
await timeoutPromise2
await timeoutPromise3
}
在这里,我们将三个Promise对象保存在变量中,这将导致与其关联的进程同时运行。此外,我们期望他们的结果-当诺言同时开始履行时,诺言也将同时完成;当您运行第二个示例时,您将在3秒钟左右看到一个模态窗口!您应该仔细测试代码,并在降低性能的同时牢记这一点。另一个不便之处是需要将预期的承诺包装在异步函数中。对类使用异步/等待
总之,我们注意到您甚至可以在用于创建类的方法中添加异步,以便它们返回promise并在其中等待promise。从有关面向对象JS的文章中获取代码,并将其与使用async修改的版本进行比较:class Person{
constructor(first, last, age, gender, interests){
this.name = {
first,
last
}
this.age = age
this.gender = gender
this.interests = interests
}
async greeting(){
return await Promise.resolve(`Hi! I'm ${this.name.first}`)
}
farewell(){
console.log(`${this.name.first} has left the building. Bye for now!`)
}
}
let han = new Person('Han', 'Solo', 25, 'male', ['Smuggling'])
该类方法可以如下使用:han.greeting().then(console.log)
浏览器支持
使用异步/等待的障碍之一是缺乏对较旧浏览器的支持。几乎所有现代浏览器都可以使用此功能。Internet Explorer和Opera Mini中存在一些问题。如果要使用async / await,但需要较旧浏览器的支持,则可以使用BabelJS库-它允许您使用最新的JS,并将其转换为适合特定浏览器的JS。结论
异步/等待允许您编写易于阅读和维护的异步代码。尽管与其他异步代码编写方法相比,对async / await的支持更差,但是绝对值得探索。感谢您的关注。编码愉快!