【问题标题】:Make asynchronous function with promises in JavaScript在 JavaScript 中使用 Promise 制作异步函数
【发布时间】:2018-01-02 06:00:57
【问题描述】:

我有一个函数

function getImage(url, key) {
  return axios({
    method: 'get',
    url,
    responseType: 'stream'
  }).then(response => {
    s3.upload({
      Key: key,
      Body: response.data,
      ContentType: response.data.headers['content-type'],
      ACL: 'public-read'
    }, (err, data) => {
      return key
    });
  }).catch(err => {
    return '';
  });
}

下载远程图像并将其上传到 Amazon S3。我希望它返回生成的密钥。

我想使用这样的功能

const images = ['http://...', 'http://...', ...].map((url, i) => {
  return {
    url: getImage(url, i)
  }
});

由于我的函数 getImage() 对于每个 URL 都可能需要一些时间,我想我将不得不使用异步调用,以便我确定函数在移动到下一个元素之前完全完成(或者是我误会了什么?)。

我想我必须使用 Promise,所以解决方案可以是这样的吗?

function getImage(url, key) {
  return new Promise((resolve, reject) => {
    return axios({
      method: 'get',
      url,
      responseType: 'stream'
    }).then(response => {
      s3.upload({
        Key: key,
        Body: response.data,
        ContentType: response.data.headers['content-type'],
        ACL: 'public-read'
      }, (err, data) => {
        resolve(key);
      });
    }).catch(err => {
      reject(err);
    });
  });
}

然后像这样使用它:

const images = ['http://...', 'http://...', ...].map((url, i) => {
  return {
    url: getImage(url, i).then(url => url).catch(err => [])
  }
});

编辑

正如 cmets 中提到的,axios 是一个承诺。那么代码应该看起来像

function getImage(url, key) {
  return axios({
    method: 'get',
    url,
    responseType: 'stream'
  }).then(response => {
    return new Promise((resolve, reject) => {
      s3.upload({
        Key: key,
        Body: response.data,
        ContentType: response.data.headers['content-type'],
        ACL: 'public-read'
      }, (err, data) => {
        if (!err) {
          resolve(key);
        } else {
          reject(err);
        }
      });
    });
  });
}

编辑 2

用例是我从公共 API 获取大量博客文章。所以我正在做类似的事情

const blogPostsOriginal = [
  { title: 'Title', images: ['url1', 'url2'] },
  { title: 'Title', images: ['url1', 'url2'] },
  { title: 'Title', images: ['url1', 'url2'] },
];

const blogPostsFormatted = blogPostsOriginal.map(blogPost => {
  return {
    title: blogPost.title,
    images: blogPost.images.map(url => {
      // upload image to S3
      return getImage(url);
    })
  };
});

那么我将如何构建博客文章数组的格式?问题是如果发生错误,我不想将图像包含在图像数组中。我不知道如何用 Promise 来检查这个。

【问题讨论】:

  • 当您调用getImage 时,它会通过axios 创建一个promise。如果您想对不同的 URL 多次使用它,那么您会很好,因为每个 Promise 都会在它被解析时返回。在这种情况下,将 axios 包装在 Promise 对象中是多余的。
  • 啊,我明白了。我已经更新了我的问题

标签: javascript node.js asynchronous promise axios


【解决方案1】:

使用 ECMAScript 2017 async / await 语法,您可以很容易地完成此操作。修改您声称有效的脚本的原始形式,它将如下所示:

async function getImage(url, key) {
  try {
    const response = await axios({
      method: 'get',
      url,
      responseType: 'stream'
    })

    await s3.upload({
      Key: key,
      Body: response.data,
      ContentType: response.data.headers['content-type'],
      ACL: 'public-read'
    }).promise()
  } catch (error) {
    // return error
    return key // since we don't have axios and s3 in here
  }

  return key
}

const blogPostsOriginal = [
  { title: 'Title', images: ['url1', 'url2'] },
  { title: 'Title', images: ['url1', 'url2'] },
  { title: 'Title', images: ['url1', 'url2'] },
];

Promise.all(blogPostsOriginal.map(async ({ title, images }) => {
  return {
    title,
    images: (await Promise.all(images.map(async (url) => {
      // get your key here somehow
      const key = Math.random().toString(36).slice(2, 12).padStart(10, 0)
      // upload image to S3
      return getImage(url, key)
    })))
    // after awaiting array of results, filter out errors
    .filter(result => !(result instanceof Error))
  }
})).then(blogPostsFormatted => {
  // use blogPostsFormatted here
  console.log(blogPostsFormatted)
})

为了解释关于s3.upload(...).promise() 的内容,我从文档herehere 中得到。

参考文献

【讨论】:

  • 谢谢!看起来不错,但我仍然不确定如何在我的用例中实现它。你能帮我解决我第二次编辑的问题吗?
  • 另一个快速的问题。您的原始代码在未获取或上传的图像上静默失败,并返回一个空白字符串作为键。您修改后的代码返回单独的承诺,根据他们的成功解决或拒绝,但您没有任何东西等待他们​​共同履行,这就是我使用Promise.all()的原因,但如果有任何一个,这将导致所有承诺都失败他们失败了。你没问题吗?
  • 如果一个图像失败,我只想将它从阵列中删除。所以我会尽可能多地保留图像。我想如果发生这种情况,我将不得不返回null,然后使用.filter(element => !!element) 或类似的东西来删除那些不起作用的东西,但这似乎有点hacky
  • 当我使用images: (await Promise.all(blogPost.images.map(async (url) => getImage(url) ))).filter(result => !(result instanceof Error)),时,我得到SyntaxError: Unexpected identifier
  • 请注意我的回答中 await 周围的括号。这些都是必要的。
猜你喜欢
  • 2020-08-13
  • 2018-01-14
  • 2019-10-23
  • 2020-01-22
  • 1970-01-01
  • 2021-02-15
  • 2021-06-28
  • 2019-11-06
  • 2017-10-29
相关资源
最近更新 更多