async/await
把咱们从回调地狱中解放了出来,可是,人们也对其很有微词.由于随之而来致使了async/await
地狱的诞生.promise
在这篇文章,我会试图解释什么是async/await
地狱,另外我也会分享一些避开它们的方法.并发
当咱们在编写JavaScript异步代码的时候,人们常常在一个接着一个的函数调用前面添加await
关键字.这会致使性能问题,由于在一般状况下,一个语句的执行并不依赖前一个语句的执行,可是由于添加了await
关键字,你仍旧须要等待前一个语句执行完才能执行一个语句.异步
假设你写一段代码用来购买披萨和饮料,这段代码以下所示.async
(async () => {
const pizzaData = await getPizzaData() // async call
const drinkData = await getDrinkData() // async call
const chosenPizza = choosePizza() // sync call
const chosenDrink = chooseDrink() // sync call
await addPizzaToCart(chosenPizza) // async call
await addDrinkToCart(chosenDrink) // async call
orderItems() // async call
})()
复制代码
从表面上看,这段代码语法是正确的,而且可以运行.可是,这并非一个好的实现,由于它剔除了并发执行.接下来让咱们了解一下这段代码是作什么的,这样咱们更加明确其中的问题所在.函数
咱们把这段代码包裹在了一个异步的当即执行函数里面.下面的事情会按次序发生:oop
就像我在前面提到的那样,全部的语句都是一行接着一行执行的,这里不存在并发执行的状况.让咱们仔细想一想,为何咱们在获取饮料列表以前须要等待披萨列表的返回?咱们应该尝试同时获取饮料和披萨的列表.然而,当咱们须要选择披萨的时候,咱们须要先获取披萨的列表.饮料也是如此.性能
所以咱们肯定,披萨相关的工做和饮料相关的工做可以同时执行,可是披萨相关的每一步工做须要按次序执行.(顺序执行)ui
这段JavaScript代码会得到购物车里面的物品,而后发送确认订单的请求.spa
async function orderItems() {
const items = await getCartItems() // async call
const noOfItems = items.length
for(var i = 0; i < noOfItems; i++) {
await sendRequest(items[i]) // async call
}
}
复制代码
在这种状况下,for循环在执行下一轮循环以前须要等待当前的sendRequest()
执行完成.然而,实际上咱们不须要等待.咱们但愿尽量快的发送全部请求而后等待他们都执行完成.
我但愿你如今可以清晰的理解什么是async/await
地狱以及它们对你程序的性能影响有多严重.如今,我想问你一个问题
若是你忘记在异步函数调用的前面添加await
关键字,这时函数开始执行了,这意味着await
并非函数执行的必要条件.这个异步函数会返回一个promise
,这个promise
咱们能够在以后使用.
(async () => {
const value = doSomeAsyncTask()
console.log(value) // an unresolved promise
})()
复制代码
结果就是,编译器不知道你须要等待这个函数执行完成,所以编译器会在这个异步任务尚未完成的时候退出这个程序.所以咱们须要await
关键字.
promise
有一个有趣的性质是你能够在前面的代码获得这个promise
, 而后在后面的代码中等待这个promise
完成.这是从async/await
地狱中解脱的关键.
(async () => {
const promise = doSomeAsyncTask()
const value = await promise
console.log(value) // the actual value
})()
复制代码
正如你所见到的那样,doSomeAsyncTask()
返回了一个promise.这个时候,doSomeAsyncTask()
已经开始执行了.为了获得这个promise的结果值,咱们能够在这个promise前面添加await
,JavaScript将会马上停在这里再也不执行下一行代码,直到得到了这个promise的返回值,再执行下一行代码.
你应该跟随如下步骤来逃离async/await
地狱.
在咱们第一个例子里面,咱们在选择披萨和饮料.于是咱们得出结论,在选择披萨以前,咱们须要得到披萨的列表.同时,在把披萨加入到购物车以前,咱们须要选择披萨.能够认为这三个步骤是互相依赖的,咱们不能在前一个步骤完成以前执行下一个任务. 可是,若是咱们拓宽一下眼界,就会发现选择披萨并不会依赖于选择饮料,咱们能够同时选择他们.这就是机器能作的比咱们更好的地方. 至此,咱们已经发现了一些语句依赖于其余的语句执行,可是另一些语句不依赖.
正如咱们所看到的,选择披萨须要几个互相依赖的语句,如得到披萨列表,选择其中一个披萨而后添加到购物车中.咱们应该把这些语句整合在一个异步函数里面.这样咱们将会获得两个异步函数,selectPizza()
和selectDrink()
咱们将利用event loop的优点来并发执行这些非阻塞异步函数.为了达成这个目标,咱们经常使用的方法是先返回promise而后使用Promise.all方法.
根据前面提到的三个步骤,咱们把他们运用的咱们的例子中.
async function selectPizza() {
const pizzaData = await getPizzaData() // async call
const chosenPizza = choosePizza() // sync call
await addPizzaToCart(chosenPizza) // async call
}
async function selectDrink() {
const drinkData = await getDrinkData() // async call
const chosenDrink = chooseDrink() // sync call
await addDrinkToCart(chosenDrink) // async call
}
(async () => {
const pizzaPromise = selectPizza()
const drinkPromise = selectDrink()
await pizzaPromise
await drinkPromise
orderItems() // async call
})()
// 我更喜欢下面这种实现.
(async () => {
Promise.all([selectPizza(), selectDrink()]).then(orderItems) // async call
})()
复制代码
如今咱们已经把这些语句整合到两个函数中,在每个函数里面,每个语句的执行依赖于前一个函数的执行.而后咱们并发的执行selectPizza()
和selectDrink()
.
在第二个例子里面,咱们须要解决未知数量的promise.解决这种状况很是简单:咱们只须要建立一个数组而后把promise存入其中.而后使用Promise.all()
方法,就可以并发的等待全部的promise返回结果.
async function orderItems() {
const items = await getCartItems() // async call
const noOfItems = items.length
const promises = []
for(var i = 0; i < noOfItems; i++) {
const orderPromise = sendRequest(items[i]) // async call
promises.push(orderPromise) // sync call
}
await Promise.all(promises) // async call
}
// 我更喜欢下面这种实现
async function orderItems() {
const items = await getCartItems() // async call
const promises = items.map((item) => sendRequest(item))
await Promise.all(promises) // async call
}
复制代码
我喜欢这篇文章可以帮助你脱离async/await
基础使用者的行列,同时可以帮助你提升你的程序性能. 若是你喜欢这篇文章,但愿可以点赞并收藏.