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 }