最近在写一个Node.js程序,功能是下载页面上的资源,首先拿到页面资源连接列表,如:css
[ 'https://xxx.com/img/logo.jpg', 'https://xxx.com/img/bg.jpg', 'https://xxx.com/css/main.css', 'https://xxx.com/css/animate.css', 'https://xxx.com/js/jquery.js', 'https://xxx.com/js/form.js', ... ]
要求是资源并行下载,全部资源下载结束后通知,收集错误的下载连接。jquery
若是是传统作法是遍历数组发送请求,声明一个变量记录请求数,无论成功或失败,结束后都给这个变量+1,而且调用一个函数,这个函数判断当前变量是否等于数组长度,相等则表示全部请求已经完成。ajax
// pseudo code var count = 0 var errs = [] var data = [...] function request(url) { ajax({url: url}) .success(function () { count++ callback() }) .fail(function () { count++ errs.push(...) callback() }) } function callback() { if (count === data.length) { console.log('done!') } } data.forEach(request)
由于请求是异步的,咱们也没法肯定每一个请求花费的时间,因此只能在回调里处理。如今咱们有了Promise
,async-await
,支持同步的写法,那能够怎么写呢?数组
咱们用setTimeout
来模拟请求,数据data = [500, 400, 300, 200, 100]
既是每一个请求返回的数据也是每一个请求所需的时间。并发
若是是继发请求(一个请求结束后再请求后一个),那么应该是按顺序打印,理论上全部请求的总时间等于每一个请求所花时间之和,约等于1500ms
;若是是并发请求(假设请求数不会太多,不超过限制),顺序是按时间从小到大打印,理论上全部请求的总时间等于最长的那个时间,约等于500ms
。异步
首先先看下怎么并行请求和请求结束肯定async
// 模拟请求 function request(param) { return new Promise(resolve => { setTimeout(() => { console.log(param) resolve() }, param) }) } const items = [500, 400, 300, 200, 100]
✘ 直接for循环函数
(() => { for (let item of items) { request(item) } console.log('end') })() // 输出:end, 100, 200, 300, 400, 500
上面的输出能够看出,请求是并行的,可是没法肯定什么结束url
✘ for循环,使用async-awaitcode
(async () => { for (let item of items) { await request(item) } console.log('end') })() // 输出:500, 400, 300, 200, 100, end
上面的代码能够看出,虽然肯定告终束,但请求是继发的
✔ 使用Promise.all
(() => { Promise.all(items.map(request)).then(res => { console.log('end') }) })() // 输出:100, 200, 300, 400, 500, end
上面的代码能够看出,请求是并发的,而且在全部请求结束后打印end
,知足条件
咱们不能保证全部的请求都是正常的,接下来看看当有请求出错时怎么处理,假设200
的请求出错
function request(param) { return new Promise((resolve, reject) => { setTimeout(() => { if (param === 200) { // console.log(param, ' failed') return reject({ status: 'error', data: param }) } // console.log(param, ' success') resolve({ status: 'success', data: param }) }, param) }) } const items = [500, 400, 300, 200, 100]
Promise
有catch
方法捕获错误,最近新增的finally
方法能在最后执行
(() => { Promise.all(items.map(request)) .then(res => { console.log(res) }) .catch (err => { console.log(err) }) .finally(res => { console.log('end', res) }) })() // 输出 {status: "error", data: 200}, end, undefined
上面的输出能够看出,若是有错误,则不会进入then
,而是进入catch
,而后进入finally
,可是finally
不接受参数,只告诉你结束了。若是把上面模拟请求的console.log(...)
注释去掉,还会发现finally
是在catch
结束后就执行了,而200
后面的请求还未结束。
接下来咱们改造下模拟请求,在请求出错后就catch
错误
function request(param) { return new Promise((resolve, reject) => { setTimeout(() => { if (param === 200) { // console.log(param, ' failed') return reject({ status: 'error', data: param }) } // console.log(param, ' success') resolve({ status: 'success', data: param }) }, param) }).catch(err => err) } (() => { Promise.all(items.map(request)) .then(res => { console.log(res, 'end') }) })() // 输出 [{…}, {…}, {…}, {stauts: 'error', data: 200}, {…}], end
这样就能够在then
中拿到所有的结果了,若是要用for
循环的话也是能够的
(async () => { const temp = [] // 这个for循环的做用和上面的map相似 for (let item of items) { temp.push(request(item)) } const result = [] for (let t of temp) { result.push(await t) } console.log(result, 'end') })() // 输出与上面一致
第一个for
循环保证并发请求,保存了Promise
,第二个循环加入await
保证按顺序执行。
好了,以上就是所有内容,你有更好的写法吗?