本文始发于我的博文手把手教你Promise小技巧——小程序接口数限制下的并行再顺序执行,现转发至此。
一、前言
Promise
是一种异步编程的解决方案,在很多语言都有它的实现,笔者一直喜欢使用它去解决各种问题,简洁高效。
ECMAscript 6
原生提供了Promise
对象。本文通过小程序上传接口并发数限制下进行上传的场景,实现图片并行再串行上传,分享Promise顺序执行的一种实现。
二、实现讲解
在微信官方文档·小程序-网络使用说明中,对wx.request
、wx.uploadFile
、wx.downloadFile
的最大并发限制是 10 个。因此当遇到一个页面需要上传超过10个文件时,无法使用Promise
并发调用上传接口。
比如页面有两个产品元素,一个元素需要上传5张照片,一个元素需要上传8张照片。此时我们操作后得到两个数组,一个包含5个上传的Promise
,一个包含8个上传的Promise
。我们可以数组内并行发起,数组间则顺序执行。
这样得到的逻辑就是,先并行上传第一个元素的5张照片,上传完再并行上传第二个元素的8张照片。(当然也可以将所有的上传按10个分批再顺序执行)
接下里我们一步步,从浅到深,分享下处理的思路。
1. 顺序执行两个Promise
在两个异步操作的情况下,使用then
将两个Promise
串起来:
a.then( _ => b );
2. 顺序执行多个Promise
在异步操作个数不确定的情况下,可以使用reduce
将它们串起来:
// promiseArray 是数组,数组内对象是 Promise 类型
promiseArray.reduce( (p, f) => p.then(_ => f) );
使用reduce
便是实现顺序执行的基础,理解清楚这行代码发生了什么,处理其他问题便可迎刃而解。
此处其实是第1点的高阶函数写法,同样是执行完前一个Promise
之后,执行后一个Promise
,然后将结果作为Result
进行下一次循环。因此需要注意到,最终结果只包含了最后一个Promise
的执行结果,没有其他promise
的执行结果。
3. 顺序执行并获取所有结果
显然,我们是需要得到所有顺序执行的结果的,因此需要进行一定的处理。
我们可以对reduce
内每一步的结果进行处理,组合成一个数组,此时需要用到Promise.all
。
// promiseArray 是数组,数组内对象是 Promise 类型
promiseArray.reduce(
(p, f) => p.then(
res => Promise
.all([Promise.resolve(res), f])
.then( res => Promise.resolve( res[0].concat([res[1]]) )
)
), Promise.resolve([]));
// 注意此处需要提供初始值,否则会出现不符合预期的结果
这是最终的处理,下面一步步讲解下。
上面第一个res
是数组类型,存放了前面每个Promise
执行成功后返回的结果。
Promise.all([Promise.resolve(res), f]);
此代码是用第一个res
构造一个Promise
,和f
一起并行执行,最终会得到第二个res
,它是一个数组,数组第一个对象是第一个res
,第二个对象是f
成功返回的数据。
Promise.resolve( res[0].concat([res[1]]);
此处将第二个res
的第一个对象(数组类型)和第二个对象合并成一个新数组,并构造成一个Promise
。
因此,最终会得到一个数组,该数组包含所有需要顺序执行的Promise
的结果。
举例如下:
let promise1 = Promise.resolve('1');
let promise2 = Promise.resolve('2');
let promise3 = Promise.resolve('3');
[promise1, promise2, promise3].reduce((p, f) => p.then(res => Promise
.all([Promise.resolve(res), f])
.then( res => Promise.resolve(res[0].concat([res[1]])) )
), Promise.resolve([])).then( res => {
console.log(res);
});
运行结果:
> Array ["1", "2", "3"]
4. 先并行再顺序执行得到所有结果
理解了第3点,那么我们只需要将每个顺序执行的Promise
,替换成多个并行执行的Promise
。
举例如下:
let promise1 = Promise.all([Promise.resolve('11'), Promise.resolve('12'), Promise.resolve('13')]);
let promise2 = Promise.all([Promise.resolve('21'), Promise.resolve('22'), Promise.resolve('23')]);
let promise3 = Promise.all([Promise.resolve('31'), Promise.resolve('32'), Promise.resolve('33')]);
[promise1, promise2, promise3].reduce(
(p, f) => p.then(
res => Promise.all([Promise.resolve(res), f]).then( res => Promise.resolve(res[0].concat([res[1]])) )
)
, Promise.resolve([])
)
.then( res => {
console.log(res);
});
运行结果:
> Array [Array ["11", "12", "13"], Array ["21", "22", "23"], Array ["31", "32", "33"]]
如果想要全部放一组:
let promise1 = Promise.all([Promise.resolve('11'), Promise.resolve('12'), Promise.resolve('13')]);
let promise2 = Promise.all([Promise.resolve('21'), Promise.resolve('22'), Promise.resolve('23')]);
let promise3 = Promise.all([Promise.resolve('31'), Promise.resolve('32'), Promise.resolve('33')]);
[promise1, promise2, promise3].reduce(
(p, f) => p.then(
res => Promise.all([Promise.resolve(res), f]).then( res => Promise.resolve(res[0].concat(res[1])) )
)
, Promise.resolve([])
)
.then( res => {
console.log(res);
})
运行结果:
> Array ["11", "12", "13", "21", "22", "23", "31", "32", "33"]
区别只在concat
的地方。
三、总结
其实复杂的Promise
难写,也不难写。结合reduce
,需要先考虑最终想要什么类型的数据,再对其进行处理,在强类型的语言中如ts、Swift,需要写明初始值的类型,不然会导致出错,耗费较长时间。
-END-
欢迎到我的博客交流:https://zackzheng.info