摘要:下面將簡單地描述一下一些日常常用場景加深對(duì)認(rèn)識(shí)最普遍的異步操作就是請(qǐng)求我們也可以用來簡單模擬異步請(qǐng)求。其中是必須的如果省略了程序就不能按預(yù)期得到結(jié)果。
前言
async/await 語法用看起來像寫同步代碼的方式來優(yōu)雅地處理異步操作,但是我們也要明白一點(diǎn),異步操作本來帶有復(fù)雜性,像寫同步代碼的方式并不能降低本質(zhì)上的復(fù)雜性,所以在處理上我們要更加謹(jǐn)慎, 稍有不慎就可能寫出不是預(yù)期執(zhí)行的代碼,從而影響執(zhí)行效率。下面將簡單地描述一下一些日常常用場景,加深對(duì) async/await 認(rèn)識(shí)
最普遍的異步操作就是請(qǐng)求,我們也可以用 setTimeOut 來簡單模擬異步請(qǐng)求。
相信這個(gè)場景是最常遇到,后一個(gè)請(qǐng)求依賴前一個(gè)請(qǐng)求,下面以爬取一個(gè)網(wǎng)頁內(nèi)的圖片為例子進(jìn)行描述,使用了 superagent 請(qǐng)求模塊, cheerio 頁面分析模塊,圖片的地址需要分析網(wǎng)頁內(nèi)容得出,所以必須按順序進(jìn)行請(qǐng)求。
const request = require("superagent") const cheerio = require("cheerio") // 簡單封裝下請(qǐng)求,其他的類似 function getHTML(url) { // 一些操作,比如設(shè)置一下請(qǐng)求頭信息 return superagent.get(url).set("referer", referer).set("user-agent", userAgent) } // 下面就請(qǐng)求一張圖片 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 格式的數(shù)據(jù) // 處理圖片 } handler(url)
上面就是一個(gè)簡單的獲取圖片數(shù)據(jù)的場景,圖片數(shù)據(jù)是加載進(jìn)內(nèi)存中,如果只是簡單的存儲(chǔ)數(shù)據(jù),可以用流的形式進(jìn)行存儲(chǔ),以防止消耗太多內(nèi)存。
其中 await getHTML 是必須的,如果省略了 await 程序就不能按預(yù)期得到結(jié)果。執(zhí)行流程會(huì)先執(zhí)行 await 后面的表達(dá)式,其實(shí)際返回的是一個(gè)處于 pending 狀態(tài)的 promise,等到這個(gè) promise 處于已決議狀態(tài)后才會(huì)執(zhí)行 await 后面的操作,其中的代碼執(zhí)行會(huì)跳出 async 函數(shù),繼續(xù)執(zhí)行函數(shù)外面的其他代碼,所以并不會(huì)阻塞后續(xù)代碼的執(zhí)行。
有的時(shí)候我們并不需要等待一個(gè)請(qǐng)求回來才發(fā)出另一個(gè)請(qǐng)求,這樣效率是很低的,所以這個(gè)時(shí)候就需要并發(fā)執(zhí)行請(qǐng)求任務(wù)。下面以一個(gè)查詢?yōu)槔?先獲取一個(gè)人的學(xué)校地址和家庭住址,再由這些信息獲取詳細(xì)的個(gè)人信息,學(xué)校地址和家庭住址是沒有依賴關(guān)系的,后面的獲取個(gè)人信息依賴于兩者
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 里面的異步請(qǐng)求都會(huì)并發(fā)執(zhí)行,并等到數(shù)據(jù)都準(zhǔn)備后返回相應(yīng)的按數(shù)據(jù)順序返回的數(shù)組,這里最后處理獲取信息的時(shí)間,由并發(fā)請(qǐng)求中最慢的請(qǐng)求決定,例如 getSchoolAdr 遲遲不返回?cái)?shù)據(jù),那么后續(xù)操作只能等待,就算 getHomeAdr 已經(jīng)提前返回了,當(dāng)然以上場景必須是這么做,但是有的時(shí)候我們并不需要這么做。
上面第一個(gè)場景中,我們只獲取到一張圖片,但是可能一個(gè)網(wǎng)頁中不止一張圖片,如果我們要把這些圖片存儲(chǔ)起來,其實(shí)是沒有必要等待圖片都并發(fā)請(qǐng)求回來后再處理,哪張圖片早回來就存儲(chǔ)哪張就行了
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") }
// 如果我們連存儲(chǔ)是否全部完成也不關(guān)心,也可以這么寫
let imageUrls = ["href1", "href2", "href3"] // saveImages() 連 async 都省了 function saveImages(imageUrls) { imageUrls.forEach(async imageUrl => { let img = await getImage(imageUrl) saveImage(img) }) }
可能有人會(huì)疑問 forEach 不是不能用于異步嗎,這個(gè)說法我也在剛接觸這個(gè)語法的時(shí)候就聽說過,很明顯 forEach 是可以處理異步的,只是是并發(fā)處理,map 也是并發(fā)處理,這個(gè)怎么用主要看你的實(shí)際場景,還要看你是否對(duì)結(jié)果感興趣
場景3.錯(cuò)誤處理一個(gè)請(qǐng)求發(fā)出,可以會(huì)遇到各種問題,我們是無法保證一定成功的,報(bào)錯(cuò)是常有的事,所以處理錯(cuò)誤有時(shí)很有必要, async/await 處理錯(cuò)誤也非常直觀, 使用 try/catch 直接捕獲就可以了
async function imageCrawler(url) { try { let img = await getImage(url) return img } catch (error) { console.log(error) } }
// imageCrawler 返回的是一個(gè) promise 可以這樣處理
async function imageCrawler(url) { let img = await getImage(url) return img } imageCrawler(url).catch(err => { console.log(err) })
可能有人會(huì)有疑問,是不是要在每個(gè)請(qǐng)求中都 try/catch 一下,這個(gè)其實(shí)你在最外層 catch 一下就可以了,一些基于中間件的設(shè)計(jì)就喜歡在最外層捕獲錯(cuò)誤
async function ctx(next) { try { await next() } catch (error) { console.log(error) } }場景4. 超時(shí)處理
一個(gè)請(qǐng)求發(fā)出,我們是無法確定什么時(shí)候返回的,也總不能一直傻傻的等,設(shè)置超時(shí)處理有時(shí)是很有必要的
function timeOut(delay) {
return new Promise((resolve, reject) => { setTimeout(() => { reject(new Error("不用等了,別傻了")) }, delay) })
}
async function imageCrawler(url,delay) {
try { let img = await Promise.race([getImage(url), timeOut(delay)]) return img } catch (error) { console.log(error) }
}
這里使用 Promise.race 處理超時(shí),要注意的是,如果超時(shí)了,請(qǐng)求還是沒有終止的,只是不再進(jìn)行后續(xù)處理。當(dāng)然也不用擔(dān)心,后續(xù)處理會(huì)報(bào)錯(cuò)而導(dǎo)致重新處理出錯(cuò)信息, 因?yàn)?promise 的狀態(tài)一經(jīng)改變是不會(huì)再改變的
在并發(fā)請(qǐng)求的場景中,如果需要大量并發(fā),必須要進(jìn)行并發(fā)限制,不然會(huì)被網(wǎng)站屏蔽或者造成進(jìn)程崩潰
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() // 立即到并發(fā)上限 } } run() return await p }總結(jié)
以上列舉了一些日常場景處理的代碼片段,在遇到比較復(fù)雜場景時(shí),可以結(jié)合以上的場景進(jìn)行組合使用,如果場景過于復(fù)雜,最好的辦法是使用相關(guān)的異步代碼控制庫。如果想更好地了解 async/await 可以先去了解 promise 和 generator, async/await 基本上是 generator 函數(shù)的語法糖,下面簡單的描述了一下內(nèi)部的原理。
function delay(time) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(time) }, time) }) } function *createTime() { let time1 = yield delay(1000) let time2 = yield delay(2000) let time3 = yield delay(3000) console.log(time1, time2, time3) } let iterator = createTime() console.log(iterator.next()) console.log(iterator.next(1000)) console.log(iterator.next(2000)) console.log(iterator.next(3000)) // 輸出 { value: Promise {}, done: false } { value: Promise { }, done: false } { value: Promise { }, done: false } 1000 2000 3000 { value: undefined, done: true }
可以看出每個(gè) value 都是 Promise,并且通過手動(dòng)傳入?yún)?shù)到 next 就可以設(shè)置生成器內(nèi)部的值,這里是手動(dòng)傳入,我只要寫一個(gè)遞歸函數(shù)讓其自動(dòng)添進(jìn)去就可以了
function run(createTime) { let iterator = createTime() let result = iterator.next() function autoRun() { if (!result.done) { Promise.resolve(result.value).then(time => { result = iterator.next(time) autoRun() }).catch(err => { result = iterator.throw(err) autoRun() }) } } autoRun() } run(createTime)
promise.resove 保證返回的是一個(gè) promise 對(duì)象 可迭代對(duì)象除了有 next 方法還有 throw 方法用于往生成器內(nèi)部傳入錯(cuò)誤,只要生成內(nèi)部能捕獲該對(duì)象,生成器就可以繼承運(yùn)行,類似下面的代碼
function delay(time) { return new Promise((resolve, reject) => { setTimeout(() => { if (time == 2000) { reject("2000錯(cuò)誤") } resolve(time) }, time) }) } function *createTime() { let time1 = yield delay(1000) let time2 try { time2 = yield delay(2000) } catch (error) { time2 = error } let time3 = yield delay(3000) console.log(time1, time2, time3) }
可以看出生成器函數(shù)其實(shí)和 async/await 語法長得很像,只要改一下 async/await 代碼片段就是生成器函數(shù)了
async function createTime() { let time1 = await delay(1000) let time2 try { time2 = await delay(2000) } catch (error) { time2 = error } let time3 = await delay(3000) console.log(time1, time2, time3) } function transform(async) { let str = async.toString() str = str.replace(/asyncs+(function)s+/, "$1 *").replace(/await/g, "yield") return str }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/114631.html
摘要:下面將簡單地描述一下一些日常常用場景加深對(duì)認(rèn)識(shí)最普遍的異步操作就是請(qǐng)求我們也可以用來簡單模擬異步請(qǐng)求。其中是必須的如果省略了程序就不能按預(yù)期得到結(jié)果。 前言 async/await 語法用看起來像寫同步代碼的方式來優(yōu)雅地處理異步操作,但是我們也要明白一點(diǎn),異步操作本來帶有復(fù)雜性,像寫同步代碼的方式并不能降低本質(zhì)上的復(fù)雜性,所以在處理上我們要更加謹(jǐn)慎, 稍有不慎就可能寫出不是預(yù)期執(zhí)行的代...
摘要:下面將簡單地描述一下一些日常常用場景加深對(duì)認(rèn)識(shí)最普遍的異步操作就是請(qǐng)求我們也可以用來簡單模擬異步請(qǐng)求。其中是必須的如果省略了程序就不能按預(yù)期得到結(jié)果。 前言 async/await 語法用看起來像寫同步代碼的方式來優(yōu)雅地處理異步操作,但是我們也要明白一點(diǎn),異步操作本來帶有復(fù)雜性,像寫同步代碼的方式并不能降低本質(zhì)上的復(fù)雜性,所以在處理上我們要更加謹(jǐn)慎, 稍有不慎就可能寫出不是預(yù)期執(zhí)行的代...
摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:異步函數(shù)是值通過事件循環(huán)異步執(zhí)行的函數(shù),它會(huì)通過一個(gè)隱式的返回其結(jié)果。 async 異步函數(shù) 不完全使用攻略 前言 現(xiàn)在已經(jīng)到 8012 年的尾聲了,前端各方面的技術(shù)發(fā)展也層出不窮,VueConf TO 2018 大會(huì) 也發(fā)布了 Vue 3.0的計(jì)劃。而在我們(我)的日常中也經(jīng)常用 Vue 來編寫一些項(xiàng)目。那么,就少不了 ES6 的登場了。那么話說回來,你真的會(huì)用 ES6 的 asyn...
摘要:調(diào)用棧被清空,消息隊(duì)列中并無任務(wù),線程停止,事件循環(huán)結(jié)束。不確定的時(shí)間點(diǎn)請(qǐng)求返回,將設(shè)定好的回調(diào)函數(shù)放入消息隊(duì)列。調(diào)用棧執(zhí)行完畢執(zhí)行消息隊(duì)列任務(wù)。請(qǐng)求并發(fā)回調(diào)函數(shù)執(zhí)行順序無法確定。 異步編程 JavaScript中異步編程問題可以說是基礎(chǔ)中的重點(diǎn),也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執(zhí)行的時(shí)候是從上到下按順序執(zhí)行,一段代碼執(zhí)行了之后才會(huì)執(zhí)行下一段代碼,這種方式...
閱讀 3220·2021-09-30 09:48
閱讀 3497·2021-09-22 16:00
閱讀 1070·2019-08-30 13:08
閱讀 3110·2019-08-30 10:53
閱讀 2421·2019-08-29 18:33
閱讀 1596·2019-08-29 12:47
閱讀 904·2019-08-29 12:16
閱讀 1935·2019-08-26 12:02