async/await異步操作的使用場景


場景1.一個請求接着一個請求

案例:后一個請求依賴前一個請求,下面以爬取一個網頁內的圖片為例,使用了superagent請求模塊,cheerio頁面分析模塊,圖片的地址需要分析網頁內容得出,所以必須按順序進行請求。
const request = require('superagent')
const cheerio = require('cheerio')
// 簡單封裝下請求,其他的類似

function getHTML(url) {
// 一些操作,比如設置一下請求頭信息
return superagent.get(url).set('referer', referer).set('user-agent', userAgent)
}
// 下面就請求一張圖片
async function imageCrawler(url) {
let res = await getHTML(url)
let html = res.text
let $ = cheerio.load(html)
let $img = $(selector)[0]
let href = $img.attribs.src
res = await getImage(href)
retrun res.body
}
async function handler(url) {
let img = await imageCrawler(url)
console.log(img) // buffer 格式的數據
// 處理圖片
}

handler(url)
其中await getHTML是必須的,如果省略了await程序就不能按期得到結果。執行流程會先執行await后面的表達式,其實際返回的是一個處於pending狀態的promise,等到這個promise處於已決議狀態后才會執行await后面的操作,其中的代碼執行會跳出async函數,繼續執行函數外面的其他代碼,所以並不會阻塞后續代碼的執行。

場景2.並發請求

有時候我們並不需要等待一個請求回來才發出另一個請求,這樣效率很低,所以這時候需要並發執行請求任務。下面以一個查詢為例,先獲取一個人的學校地址和家庭住址,再由這些信息獲取詳細的個人信息,學校地址和家庭住址是沒有依賴關系的,后面的獲取個人信息依賴於兩者
async function infoCrawler(url, name) {
let [schoolAdr, homeAdr] = await Promise.all([getSchoolAdr(name), getHomeAdr(name)])
let info = await getInfo(url + ?schoolAdr=${schoolAdr}&homeAdr=${homeAdr})
return info
面使用的 Promise.all 里面的異步請求都會並發執行,並等到數據都准備后返回相應的按數據順序返回的數組,這里最后處理獲取信息的時間,由並發請求中最慢的請求決定,例如 getSchoolAdr 遲遲不返回數據,那么后續操作只能等待,就算 getHomeAdr 已經提前返回了,當然以上場景必須是這么做,但是有的時候我們並不需要這么做。
上面第一個場景中,我們只獲取到一張圖片,但是可能一個網頁中不止一張圖片,如果我們要把這些圖片存儲起來,其實是沒有必要等待圖片都並發請求回來后再處理,哪張圖片早回來就存儲哪張就行了
let imageUrls = ['href1', 'href2', 'href3']
async function saveImages(imageUrls) {
await Promise.all(imageUrls.map(async imageUrl => {
let img = await getImage(imageUrl)
return await saveImage(img)
}))
console.log('done')
}
// 如果我們連存儲是否全部完成也不關心,也可以這么寫
let imageUrls = ['href1', 'href2', 'href3']
// saveImages() 連 async 都省了
function saveImages(imageUrls) {
imageUrls.forEach(async imageUrl => {
let img = await getImage(imageUrl)
saveImage(img)
})
}
可能有人會疑問 forEach 不是不能用於異步嗎,這個說法我也在剛接觸這個語法的時候就聽說過,
很明顯 forEach 是可以處理異步的,只是是並發處理,map 也是並發處理,這個怎么用主要看你的
實際場景

場景3.錯誤處理

一個請求發出,可以會遇到各種問題,報錯是常有的事,所以處理錯誤有時很有必要,async/await處理錯誤也非常直觀,使用try/catch直接捕獲
async function imageCrawler(url) {
try {
let img = await getImage(url)
return img
} catch (error) {
console.log(error)
}
}
// imageCrawler 返回的是一個 promise 可以這樣處理
async function imageCrawler(url) {
let img = await getImage(url)
return img
}
imageCrawler(url).catch(err => {
console.log(err)
})
可能有人會疑問,是不是要在每個請求中都try/catch一下,這個其實在最外層catch一下就好了,一些中間件的設計就喜歡在最外層捕獲錯誤
async function ctx(next) {
try {
await next()
} catch (error) {
console.log(error)
}
}

超時處理

一個請求發出,我們是無法確定什么時候能返回的,也總不能一直傻等,設置超時處理有時候是很有必要的
function timeOut(delay){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(new Error('已超時'))
})
})
}
async function imageCrawler(url,delay) {
try {
let img = await Promise.race([getImage(url), timeOut(delay)])
return img
} catch (error) {
console.log(error)
}
}

並發限制

在並發請求的場景中,如果需要大量並發,必須要進行並發限制,不然會被網站屏蔽或者造成進程奔潰
async function getImages(urls, limit) {
let running = 0
let r
let p = new Promise((resolve, reject) => {
r = resolve
})
function run() {
if (running < limit && urls.length > 0) {
running++
let url = urls.shift();
(async () => {
let img = await getImage(url)
running--
console.log(img)
if (urls.length === 0 && running === 0) {
console.log('done')
return r('done')
} else {
run()
}
})()
run() // 立即到並發上限
}
}
run()
return await p
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM