国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

詳解async/await 異步應用的常用場景

2019-11-19 11:35:32
字體:
來源:轉載
供稿:網友

前言

async/await 語法用看起來像寫同步代碼的方式來優雅地處理異步操作,但是我們也要明白一點,異步操作本來帶有復雜性,像寫同步代碼的方式并不能降低本質上的復雜性,所以在處理上我們要更加謹慎, 稍有不慎就可能寫出不是預期執行的代碼,從而影響執行效率。下面將簡單地描述一下一些日常常用場景,加深對 async/await 認識
最普遍的異步操作就是請求,我們也可以用 setTimeOut 來簡單模擬異步請求。

場景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)  }}

場景4. 超時處理

一個請求發出,我們是無法確定什么時候返回的,也總不能一直傻傻的等,設置超時處理有時是很有必要的

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 處理超時,要注意的是,如果超時了,請求還是沒有終止的,只是不再進行后續處理。當然也不用擔心,后續處理會報錯而導致重新處理出錯信息, 因為 promise 的狀態一經改變是不會再改變的

場景5. 并發限制

在并發請求的場景中,如果需要大量并發,必須要進行并發限制,不然會被網站屏蔽或者造成進程崩潰

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}

總結

以上列舉了一些日常場景處理的代碼片段,在遇到比較復雜場景時,可以結合以上的場景進行組合使用,如果場景過于復雜,最好的辦法是使用相關的異步代碼控制庫。如果想更好地了解 async/await 可以先去了解 promise 和 generator, async/await 基本上是 generator 函數的語法糖,下面簡單的描述了一下內部的原理。

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 { <pending> }, done: false } { value: Promise { <pending> }, done: false } { value: Promise { <pending> }, done: false } 1000 2000 3000 { value: undefined, done: true }

可以看出每個 value 都是 Promise,并且通過手動傳入參數到 next 就可以設置生成器內部的值,這里是手動傳入,我只要寫一個遞歸函數讓其自動添進去就可以了

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 保證返回的是一個 promise 對象 可迭代對象除了有 next 方法還有 throw 方法用于往生成器內部傳入錯誤,只要生成內部能捕獲該對象,生成器就可以繼承運行,類似下面的代碼

function delay(time) {  return new Promise((resolve, reject) => {    setTimeout(() => {      if (time == 2000) {      reject('2000錯誤')    }      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)}

可以看出生成器函數其實和 async/await 語法長得很像,只要改一下 async/await 代碼片段就是生成器函數了

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(/async/s+(function)/s+/, '$1 *').replace(/await/g, 'yield') return str}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 泰安市| 定安县| 会宁县| 壤塘县| 嘉禾县| 铅山县| 锡林浩特市| 石家庄市| 吉安县| 精河县| 磐安县| 二连浩特市| 剑河县| 富川| 凌源市| 托里县| 浑源县| 苍南县| 洛南县| 兖州市| 永清县| 庄河市| 中阳县| 离岛区| 共和县| 周至县| 弥勒县| 洞头县| 诸城市| 师宗县| 永泰县| 稷山县| 南平市| 津南区| 福海县| 公安县| 清水河县| 绥棱县| 桐城市| 黑龙江省| 东方市|