【原创】前端处理并发的最佳实践?(不考虑axios.all、axios.spread或umi.request等库方法)

2,000 阅读4分钟

1、什么是并发?

 因为js是单线程的,所以前端的并发指的是在极短时间内发送多个数据请求,比如说循环中发送ajax。

2、Promise.all优缺点,.then,.finally的区别, 如果其中有promise走了reject,能不能获取到其它resolve的promise传递过来的值?

  • 用Promise.all处理并发, 当所有promise全部成功时, 会走.then,并且可以拿到所有promise中传进resolve中的值
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
  	resolve([1, 2, 3])
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
  	resolve([4, 5, 6])
  }, 3000);
})

console.time(); // 开始计时
Promise.all([p1, p2])
.then(result => {
  console.timeEnd(); // 计时结束  default: 3000.71923828125 ms
  console.log(result); // (2) [Array(1), Array(1)]  
})
  • 用Promise.all处理并发, 当其中有一个promise执行失败, 不会走.then, 会走finally(不管成功失败都会执行), 但是因为finally回调函数中,无参数,所有拿不到执行成功的promise传进resolve的值
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
  	resolve([1, 2, 3]);
  }, 1000);
})
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
  	reject([4, 5, 6]);
  }, 3000);
})

console.time(); // 开始计时
Promise.all([p1, p2])
.then(result => {
	// 这里不会执行
    console.timeEnd(); 
    console.log(result); 
})
.finally(() => {
    console.timeEnd(); // 计时结束  default: 3000.71923828125 ms
})

总结:

  1. Promise.all在处理并发时,如果有一个promise失败,就不会走.then, 但是如果只是需要在所有异步执行完成之后去执行其它副作用代码,可以把代码写在.finally中实现。
  2. 但是如果需要在有异步失败之后,获取到promise实例通过resolve传过来的值,则不行

3、Promise.allSettled

  • 在promise.all中实现不了的功能, 可以使用promise.allSettled来实现, 在promise.allSettled中,当其中有一个promise执行失败时,会继续走.then, 并且可以同时拿到传给resolve、reject的值
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
  	resolve([1])
  }, 1000);
})
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
  	reject([2])
  }, 3000);
})
console.time(); // 开始计时
Promise.allSettled([p1, p2])
.then(result => {
    console.timeEnd(); // 计时结束  default: 3000.71923828125 ms
    console.log(result); // (2) [Array(1), Array(1)]  
    /* 展开数组中对象,  status可以拿到promis执行状态, value、reason 分别可以拿到执行成功和执行失败传过去的值
      (2) [{…}, {…}]
      0: {status: "fulfilled", value: Array(1)}
      1: {status: "rejected", reason: Array(1)}
    */
})

总结:

  1. Promise.allSettled在处理并发时,不怕异步执行失败,继续会走.then,完美解决promise.all在并发有失败情况下,拿不到值的情况
  2. 唯一的缺点,比起promise.all兼容性差一点,多了两个不支持的浏览器, 安卓端火狐浏览器(Firefox for Android)及 Samsung Internet 浏览器。

4、async、await优缺点,及async、await能处理并发吗?怎么写才能让程序不阻塞?

  • 说起处理异步的方法,肯定要说一下async、await, async、await写法相比之下语法更优雅,代码可读性更高。 但是当要处理并发的时候,能不能用async、await出来? 会不会造成程序阻塞?看下下面这个例子。
let getData1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([1, 2, 3])
    }, 2000);
  })
};

let getData2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([4, 5, 6])
    }, 3000);
  })
}
let syncFn = async () => {
    console.time(); // 开始计时
    
    const data1 = await getData1();
    const data2 = await getData2();
    
    console.timeEnd(); // default: 5008.10400390625 ms 
    console.log(data1, data2); // (3) [1, 2, 3] (3) [4, 5, 6]
}

syncFn(); 
  • 上面那种写法,最终能拿到两个异步的值, 但是发生了阻塞,总的执行时间是两个请求加起来的时间,曾经一度以为,async、await的优点就是代码优雅、可读性高等,缺点就是会造成程序的阻塞,并不适合处理并发。
  • 但是在之前有一段时间,和一个比较厉害的朋友,一起写了一个koa2的项目,(koa2框架使用Babel实现Async方法,可以在node环境中使用async、await语法,所以在koa2中很推崇使用async、await来处理异步),所以当提及这个问题的时候,他给我指点了迷津,代码如下:
let getData1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([1, 2, 3])
    }, 2000);
  })
};

let getData2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([4, 5, 6])
    }, 3000);
  })
}

let syncFn = async () => {
    console.time(); // 开始计时
    const p1 = getData1();
    const p2 = getData2();

    const data1 = await p1;
    const data2 = await p2;

    console.timeEnd(); // default: 3008.10400390625 ms 
    console.log(data1, data2); // (3) [1, 2, 3] (3) [4, 5, 6]
}
syncFn(); 

个人总结(有错误请指正):

1、如果是在安装了axios或者其它请求库,在这些库本身提供支持并发的api情况下,如axios.all、axios.spread,建议使用他们提供的这些api,因为作为一个百万级下载量的库,提供的这些api考虑到的边界条件、兼容性肯定是比其它原生方法好用的。

2、在没有这些库的情况下,推荐使用Promise.allSettled,如果你喜欢用 async、await 也可以,结果没有区别,看个人喜好了。但是在我看来在处理并发的情况下 async、await的写法感觉就不那么简洁了。