资讯专栏INFORMATION COLUMN

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

gself / 2900人阅读

摘要:最近在写一个程序,功能是下载页面上的资源,首先拿到页面资源链接列表,如要求是资源并行下载,所有资源下载结束后通知,收集错误的下载链接。如果把上面模拟请求的注释去掉,还会发现是在结束后就执行了,而后面的请求还未结束。

最近在写一个Node.js程序,功能是下载页面上的资源,首先拿到页面资源链接列表,如:

[
  "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",
  ...
]

要求是资源并行下载所有资源下载结束后通知收集错误的下载链接

如果是传统做法是遍历数组发送请求,声明一个变量记录请求数,不管成功或失败,结束后都给这个变量+1,并且调用一个函数,这个函数判断当前变量是否等于数组长度,相等则表示所有请求已经完成。

// 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

首先先看下怎么并行请求请求结束确定

// 模拟请求
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

上面的输出可以看出,请求是并行的,但是无法确定什么结束

✘ for循环,使用async-await

(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保证按顺序执行。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106106.html

相关文章

  • 非常硬核的技术知识-CopyOnWrite思想

    摘要:而且只要他更新完毕对修饰的变量赋值,那么读线程立马可以看到最新修改后的数组,这是保证的。这个时候,就采用了思想来实现这个,避免更新的时候阻塞住高频的读操作,实现无锁的效果,优化线程并发的性能。 今天聊一个非常硬核的技术知识,给大家分析一下CopyOnWrite思想是什么,以及在Java并发包中的具体体现,包括在Kafka内核源码中是如何运用这个思想来优化并发性能的。这个CopyOnW...

    amc 评论0 收藏0
  • MySQL数据库优化

    摘要:具体来说,就是在写数据库的时候同时写一份数据到缓存集群里,然后用缓存集群来承载大部分的读请求。各种精妙的架构设计因此一篇小文顶多具有抛砖引玉的效果但是数据库优化的思想差不多就这些了 前言 数据库优化一方面是找出系统的瓶颈,提高MySQL数据库的整体性能,而另一方面需要合理的结构设计和参数调整,以提高用户的相应速度,同时还要尽可能的节约系统资源,以便让系统提供更大的负荷. 1. 优化一览...

    wangshijun 评论0 收藏0
  • 史上最全阿里 Java 面试题总结

    摘要:以下为大家整理了阿里巴巴史上最全的面试题,涉及大量面试知识点和相关试题。的内存结构,和比例。多线程多线程的几种实现方式,什么是线程安全。点击这里有一套答案版的多线程试题。线上系统突然变得异常缓慢,你如何查找问题。 以下为大家整理了阿里巴巴史上最全的 Java 面试题,涉及大量 Java 面试知识点和相关试题。 JAVA基础 JAVA中的几种基本数据类型是什么,各自占用多少字节。 S...

    winterdawn 评论0 收藏0

发表评论

0条评论

gself

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<