欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時間:2019年05月13日 10:32:53   作者:changli  
這篇文章主要介紹了詳解async/await 異步應用的常用場景,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

前言

async/await 語法用看起來像寫同步代碼的方式來優(yōu)雅地處理異步操作,但是我們也要明白一點,異步操作本來帶有復雜性,像寫同步代碼的方式并不能降低本質上的復雜性,所以在處理上我們要更加謹慎, 稍有不慎就可能寫出不是預期執(zhí)行的代碼,從而影響執(zhí)行效率。下面將簡單地描述一下一些日常常用場景,加深對 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 程序就不能按預期得到結果。執(zhí)行流程會先執(zhí)行 await 后面的表達式,其實際返回的是一個處于 pending 狀態(tài)的 promise,等到這個 promise 處于已決議狀態(tài)后才會執(zhí)行 await 后面的操作,其中的代碼執(zhí)行會跳出 async 函數,繼續(xù)執(zhí)行函數外面的其他代碼,所以并不會阻塞后續(xù)代碼的執(zhí)行。

場景2.并發(fā)請求

有的時候我們并不需要等待一個請求回來才發(fā)出另一個請求,這樣效率是很低的,所以這個時候就需要并發(fā)執(zhí)行請求任務。下面以一個查詢?yōu)槔?先獲取一個人的學校地址和家庭住址,再由這些信息獲取詳細的個人信息,學校地址和家庭住址是沒有依賴關系的,后面的獲取個人信息依賴于兩者

 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 里面的異步請求都會并發(fā)執(zhí)行,并等到數據都準備后返回相應的按數據順序返回的數組,這里最后處理獲取信息的時間,由并發(fā)請求中最慢的請求決定,例如 getSchoolAdr 遲遲不返回數據,那么后續(xù)操作只能等待,就算 getHomeAdr 已經提前返回了,當然以上場景必須是這么做,但是有的時候我們并不需要這么做。

上面第一個場景中,我們只獲取到一張圖片,但是可能一個網頁中不止一張圖片,如果我們要把這些圖片存儲起來,其實是沒有必要等待圖片都并發(fā)請求回來后再處理,哪張圖片早回來就存儲哪張就行了

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 是可以處理異步的,只是是并發(fā)處理,map 也是并發(fā)處理,這個怎么用主要看你的實際場景,還要看你是否對結果感興趣

場景3.錯誤處理

一個請求發(fā)出,可以會遇到各種問題,我們是無法保證一定成功的,報錯是常有的事,所以處理錯誤有時很有必要, 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. 超時處理

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

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

場景5. 并發(fā)限制

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

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
}

總結

以上列舉了一些日常場景處理的代碼片段,在遇到比較復雜場景時,可以結合以上的場景進行組合使用,如果場景過于復雜,最好的辦法是使用相關的異步代碼控制庫。如果想更好地了解 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
}

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

相關文章

  • 一步一步教你寫帶圖片注釋的淡入淡出插件(三)

    一步一步教你寫帶圖片注釋的淡入淡出插件(三)

    接上一文,我們已經實現了圖片的自動淡入淡出播放。接下來需要加入控制器。
    2010-10-10
  • 微信小程序自定義prompt組件步驟詳解

    微信小程序自定義prompt組件步驟詳解

    本文分步驟給大家介紹了微信小程序自定義prompt組件,文中通過實例代碼相結合的形式給大家介紹的非常詳細,需要的朋友可以參考下
    2018-06-06
  • javascript中eval函數用法分析

    javascript中eval函數用法分析

    這篇文章主要介紹了javascript中eval函數用法,實例分析了javascript中eval函數的使用技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • JS+Canvas實現接球小游戲的示例代碼

    JS+Canvas實現接球小游戲的示例代碼

    本文主要為大家詳細介紹了如何利用JS+Canvas實現接球小游戲,文中的示例代碼講解詳細,對我們學習有一定的幫助,感興趣的小伙伴可以了解一下
    2022-06-06
  • js 求時間差的實現代碼

    js 求時間差的實現代碼

    下面小編就為大家?guī)硪黄猨s 求時間差的實現代碼。小編覺得挺不錯的,現在分享給大家。也給大家做個參考。一起跟隨小編過來看看吧
    2016-04-04
  • js實現限定范圍拖拽的示例

    js實現限定范圍拖拽的示例

    這篇文章主要介紹了js實現限定范圍拖拽的示例,幫助大家更好的制作js特效,美化自己的網頁,感興趣的朋友可以了解下
    2020-10-10
  • GoJs基本使用示例詳解

    GoJs基本使用示例詳解

    這篇文章主要為大家介紹了GoJs基本使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • 利用TypeScript從字符串字面量類型提取參數類型

    利用TypeScript從字符串字面量類型提取參數類型

    這篇文章主要介紹了利用TypeScript從字符串字面量類型提取參數類型,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-09-09
  • JavaScript實現拖拽簡單效果

    JavaScript實現拖拽簡單效果

    這篇文章主要為大家詳細介紹了JavaScript實現拖拽簡單效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • 微信小程序 scroll-view的使用案例代碼詳解

    微信小程序 scroll-view的使用案例代碼詳解

    這篇文章主要介紹了微信小程序 scroll-view的使用案例分析,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06

最新評論