async/await 异步运用的经常使用场景

媒介

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
}


    原文作者:changli
    原文地址: https://segmentfault.com/a/1190000019153750
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞