多个请求并发执行怎么写?

最近在写一个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)

由于请求是异步的,咱们也没法肯定每一个请求花费的时间,因此只能在回调里处理。如今咱们有了Promiseasync-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]

Promisecatch方法捕获错误,最近新增的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保证按顺序执行。

好了,以上就是所有内容,你有更好的写法吗?

相关文章
相关标签/搜索