最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解async/await 異步應(yīng)用的常用場(chǎng)景

 更新時(shí)間:2019年05月13日 10:32:53   作者:changli  
這篇文章主要介紹了詳解async/await 異步應(yīng)用的常用場(chǎng)景,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

前言

async/await 語(yǔ)法用看起來(lái)像寫(xiě)同步代碼的方式來(lái)優(yōu)雅地處理異步操作,但是我們也要明白一點(diǎn),異步操作本來(lái)帶有復(fù)雜性,像寫(xiě)同步代碼的方式并不能降低本質(zhì)上的復(fù)雜性,所以在處理上我們要更加謹(jǐn)慎, 稍有不慎就可能寫(xiě)出不是預(yù)期執(zhí)行的代碼,從而影響執(zhí)行效率。下面將簡(jiǎn)單地描述一下一些日常常用場(chǎng)景,加深對(duì) async/await 認(rèn)識(shí)
最普遍的異步操作就是請(qǐng)求,我們也可以用 setTimeOut 來(lái)簡(jiǎn)單模擬異步請(qǐng)求。

場(chǎng)景1. 一個(gè)請(qǐng)求接著一個(gè)請(qǐng)求

相信這個(gè)場(chǎng)景是最常遇到,后一個(gè)請(qǐng)求依賴(lài)前一個(gè)請(qǐng)求,下面以爬取一個(gè)網(wǎng)頁(yè)內(nèi)的圖片為例子進(jìn)行描述,使用了 superagent 請(qǐng)求模塊, cheerio 頁(yè)面分析模塊,圖片的地址需要分析網(wǎng)頁(yè)內(nèi)容得出,所以必須按順序進(jìn)行請(qǐng)求。

const request = require('superagent')
const cheerio = require('cheerio')
// 簡(jiǎn)單封裝下請(qǐng)求,其他的類(lèi)似

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è)簡(jiǎn)單的獲取圖片數(shù)據(jù)的場(chǎng)景,圖片數(shù)據(jù)是加載進(jìn)內(nèi)存中,如果只是簡(jiǎn)單的存儲(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í)行。

場(chǎng)景2.并發(fā)請(qǐng)求

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

 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)然以上場(chǎng)景必須是這么做,但是有的時(shí)候我們并不需要這么做。

上面第一個(gè)場(chǎng)景中,我們只獲取到一張圖片,但是可能一個(gè)網(wǎng)頁(yè)中不止一張圖片,如果我們要把這些圖片存儲(chǔ)起來(lái),其實(shí)是沒(méi)有必要等待圖片都并發(fā)請(qǐng)求回來(lái)后再處理,哪張圖片早回來(lái)就存儲(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)心,也可以這么寫(xiě)

let imageUrls = ['href1', 'href2', 'href3']
// saveImages() 連 async 都省了
function saveImages(imageUrls) {
  imageUrls.forEach(async imageUrl => {
  let img = await getImage(imageUrl)
  saveImage(img)
  })
}

可能有人會(huì)疑問(wèn) forEach 不是不能用于異步嗎,這個(gè)說(shuō)法我也在剛接觸這個(gè)語(yǔ)法的時(shí)候就聽(tīng)說(shuō)過(guò),很明顯 forEach 是可以處理異步的,只是是并發(fā)處理,map 也是并發(fā)處理,這個(gè)怎么用主要看你的實(shí)際場(chǎng)景,還要看你是否對(duì)結(jié)果感興趣

場(chǎng)景3.錯(cuò)誤處理

一個(gè)請(qǐng)求發(fā)出,可以會(huì)遇到各種問(wèn)題,我們是無(wú)法保證一定成功的,報(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ì)有疑問(wèn),是不是要在每個(gè)請(qǐng)求中都 try/catch 一下,這個(gè)其實(shí)你在最外層 catch 一下就可以了,一些基于中間件的設(shè)計(jì)就喜歡在最外層捕獲錯(cuò)誤

async function ctx(next) {
  try {
    await next()
  } catch (error) {
    console.log(error)
  }
}

場(chǎng)景4. 超時(shí)處理

一個(gè)請(qǐng)求發(fā)出,我們是無(wú)法確定什么時(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)求還是沒(méi)有終止的,只是不再進(jìn)行后續(xù)處理。當(dāng)然也不用擔(dān)心,后續(xù)處理會(huì)報(bào)錯(cuò)而導(dǎo)致重新處理出錯(cuò)信息, 因?yàn)?promise 的狀態(tài)一經(jīng)改變是不會(huì)再改變的

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

在并發(fā)請(qǐng)求的場(chǎ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é)

以上列舉了一些日常場(chǎng)景處理的代碼片段,在遇到比較復(fù)雜場(chǎng)景時(shí),可以結(jié)合以上的場(chǎng)景進(jìn)行組合使用,如果場(chǎng)景過(guò)于復(fù)雜,最好的辦法是使用相關(guān)的異步代碼控制庫(kù)。如果想更好地了解 async/await 可以先去了解 promise 和 generator, async/await 基本上是 generator 函數(shù)的語(yǔ)法糖,下面簡(jiǎn)單的描述了一下內(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 { <pending> }, done: false } 

{ value: Promise { <pending> }, done: false }

 { value: Promise { <pending> }, done: false } 

1000 2000 3000 

{ value: undefined, done: true }

可以看出每個(gè) value 都是 Promise,并且通過(guò)手動(dòng)傳入?yún)?shù)到 next 就可以設(shè)置生成器內(nèi)部的值,這里是手動(dòng)傳入,我只要寫(xiě)一個(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)行,類(lèi)似下面的代碼

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 語(yǔ)法長(zhǎng)得很像,只要改一下 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(/async\s+(function)\s+/, '$1 *').replace(/await/g, 'yield')
 return str
}

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論

油尖旺区| 正阳县| 江陵县| 阳高县| 浦县| 渭源县| 夏河县| 珲春市| 赞皇县| 和硕县| 江川县| 日喀则市| 葫芦岛市| 宁夏| 长岭县| 营口市| 克拉玛依市| 西安市| 加查县| 南宁市| 成都市| 铜鼓县| 灵宝市| 芦溪县| 旌德县| 兴隆县| 阆中市| 青田县| 阿巴嘎旗| 高安市| 申扎县| 利辛县| 沁水县| 久治县| 徐闻县| 综艺| 鹰潭市| 崇左市| 陵水| 诸城市| 噶尔县|