如何优雅的执行 forEach 中异步方法的完成回调

功能需求

根据标签列表循环下载对应图片

现有代码

var imageFiles = [];
data.imageTags.forEach((item, index, array) => {
  wx.downloadFile({
    url: `${ $http.baseURL }yun/image/${ item }`,
    header: $http.baseHeader,
    success: res => {
      imageFiles[index] = res.tempFilePath;
      index == array.length - 1 && that.setData({ 'data.imageFiles': imageFiles });
    }
  });
});

存在问题

部分图片过大时 imageFiles 对应为 null

请问该如果解决(不太想 var i=0; 回调成功 i++ 这种做法

阅读 7k
4 个回答

楼上都只是摆了个 Promise 架子,思路还是一样,并没有解决到实际问题呢。

题主其中一个重要问题是默认最后一个回调是最后到达的,index == array.length - 1 && ... 这个假设明显不成立。

题主应该是需要一个不会 fail 的 Promise.all,也就是 reflect,有的库提供,没有可以自行封装一个。

供参考

function downloadFile (tag) {
  return new Promise((resolve, reject) => {
    wx.downloadFile({
      url: `${ $http.baseURL }yun/image/${ tag }`,
      header: $http.baseHeader,
      success: res => resolve(res.tempFilePath)
    });
  })
}

function promiseReflect (iterable) {
  if (!Array.isArray(iterable)) {
    iterable = Array.from(iterable);
  }
  return Promise.all(iterable.map(p => typeof p.catch === 'function' ? p.catch(() => null) : p));
}

promiseReflect(imageTags.map(tag => downloadFile(tag)))
  .then(imageFiles => {
    // 可以 filter 掉失败的图片
    that.setData({ 'data.imageFiles': imageFiles });
  })

首先利用promise将wx.downloadFile封装一下

function downloadFile(options) {
    return new Promise((resolve, reject) => {
        wx.downloadFile({
            url: options.url,
            header: options.header,
            success: res => resolve(res),
            fail: err => reject(err)
        })
    })
}

然后利用async/await的语法进行循环

data.imageTags.forEach(async (item, index, array) => {
    const res = await downloadFile({
        url: `${ $http.baseURL }yun/image/${ item }`,
        header: $http.baseHeader
    });

    imageFiles[index] = res.tempFilePath;
    index == array.length - 1 && that.setData({ 'data.imageFiles': imageFiles });
})

重在提供思路,不保证能完全正确运行,不记得wx小程序里是否已经支持async语法

我也遇到过类似的循环+异步的问题,目前我知道的有以下几种解决法:

  • async类库的eachSeries方法

  • Promise

  • 使用js的async/await方法

我给一个比较完整的代码,因为小程序当前允许我们基于ES6来开发,所以这个问题,如果使用ES6来写,大概是这样:

let promises = [];
var imageFiles = [];
data.imageTags.forEach((item, index, array) => {
    const _p = new Promise((resolve, reject) => {
          wx.downloadFile({
            url: `${ $http.baseURL }yun/image/${ item }`,
            header: $http.baseHeader,
            success: res => {
              imageFiles[index] = res.tempFilePath;
              index == array.length - 1 && that.setData({ 'data.imageFiles': imageFiles });
              resolve({ item: item, file: res.tempFilePath }); // 参数
            },
            error: err => {
                reject({ err: err, item: item });
            }
          });
    });
    promises.push(_p);
});

Promise.all(promises).then(data => {
    // data: 数组参数,每一个 promise 的结果。
}).catch(err => {

});

Happy coding!

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题