使用 Promise.all() 获取带有 await 语句的 url 列表

S. *_*enk 3 javascript

tl; dr - 如果您必须过滤承诺(例如错误的承诺),请不要使用异步函数

我正在尝试使用异步获取 url 列表并解析它们,问题是如果我在获取时其中一个 url 出现错误 - 假设由于某种原因 api 端点不存在 - 程序用明显的错误粉碎解析:

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: ext is not iterable
Run Code Online (Sandbox Code Playgroud)

我试过检查 res.json() 是否未定义,但显然不是这样,因为它抱怨整个 'ext' 承诺数组不可迭代。

async function fetchAll() {
  let data
  let ext
  try {
    data = await Promise.all(urls.map(url=>fetch(url)))
  } catch (err) {
    console.log(err)
  }
  try {
    ext = await Promise.all(data.map(res => {
      if (res.json()==! 'undefined') { return res.json()}
    }))
  } catch (err) {
    console.log(err)
  }
  for (let item of ext) {
    console.log(ext)
  }
}
Run Code Online (Sandbox Code Playgroud)

问题 1:

如何解决上述问题,以免在无效地址上崩溃?

问题2:

我的下一步是将提取的数据写入数据库。假设内容的数据大小为 2-5mgb,我使用 Promise.all() 内存的方法是否有效?或者它会更有效地使用内存,否则编写一个 for 循环来处理每个提取,然后在同一次迭代中写入数据库,然后才处理下一次提取?

Rob*_*ell 10

从根本上讲,您的代码有几个问题。我们应该按顺序解决这些问题,首先是您没有传递任何 URL!

async function fetchAll(urls) {
  let data
  let ext
  try {
    data = await Promise.all(urls.map(url=>fetch(url)))
  } catch (err) {
    console.log(err)
  }
  try {
    ext = await Promise.all(data.map(res => {
      if (res.json()==! 'undefined') { return res.json()}
    }))
  } catch (err) {
    console.log(err)
  }
  for (let item of ext) {
    console.log(ext)
  }
}
Run Code Online (Sandbox Code Playgroud)

首先,您在相关数据上有几个 try catch 块。它们都应该在一个 try catch 块中:

async function fetchAll(urls) {
  try {
    let data = await Promise.all(urls.map(url=>fetch(url)))
    let ext = await Promise.all(data.map(res => {
      // also fixed the ==! 'undefined'
      if (res.json() !== undefined) { return res.json()}
    }))
    for (let item of ext) {
      console.log(ext)
    }
  } catch (err) {
    console.log(err)
  }
}
Run Code Online (Sandbox Code Playgroud)

接下来是 res.json() 返回一个包裹在对象周围的承诺(如果它存在的话)的问题

if (res.json() !== undefined) { return res.json()}
Run Code Online (Sandbox Code Playgroud)

这不是您应该如何使用 .json() 方法。如果没有可解析的 json,它将失败。你应该在它上面放一个 .catch

async function fetchAll(urls) {
  try {
    let data = await Promise.all(urls.map(url => fetch(url).catch(err => err)))
    let ext = await Promise.all(data.map(res => res.json ? res.json().catch(err => err) : res))
    for (let item of ext) {
      console.log(ext)
    }
  } catch (err) {
    console.log(err)
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,当它无法获取 URL 或解析 JSON 时,您将收到错误,并且它会向下级联而不会抛出。现在你的 try catch 块只会在发生不同的错误时抛出。

当然,这意味着我们在每个 promise 上放置了一个错误处理程序并级联错误,但这并不是一件坏事,因为它允许所有提取发生,并且您可以区分哪些提取失败。这比仅仅为所有提取拥有一个通用处理程序而不知道哪个失败要好得多。

但是现在我们有了它的形式,我们可以看到可以对代码执行一些更好的优化

async function fetchAll(urls) {
  try {
    let ext = await Promise.all(
      urls.map(url => fetch(url)
        .then(r => r.json())
        .catch(error => ({ error, url }))
      )
    )
    for (let item of ext) {
      console.log(ext)
    }
  } catch (err) {
    console.log(err)
  }
}
Run Code Online (Sandbox Code Playgroud)

现在有了更小的占用空间、更好的错误处理以及可读、可维护的代码,我们可以决定我们最终想要返回的内容。现在该函数可以存在于任何地方,可以被重用,它所需要的只是一个简单的 GET URL 数组。

下一步是对它们做一些事情,所以我们可能想要返回数组,它将被包装在一个承诺中,实际上我们希望错误冒泡,因为我们已经处理了每个 fetch 错误,所以我们还应该删除 try catch . 在这一点上,让它异步不再有帮助,反而会造成伤害。最终,我们得到了一个小函数,它将所有 URL 解析或错误与其各自的 URL 分组在一起,我们可以轻松地对其进行过滤、映射和链接!

function fetchAll(urls) {
  return Promise.all(
    urls.map(url => fetch(url)
      .then(r => r.json())
      .then(data => ({ data, url }))
      .catch(error => ({ error, url }))
    )
  )
}
Run Code Online (Sandbox Code Playgroud)

现在我们得到一个类似对象的数组,每个对象都有它获取的 url,以及数据或错误字段!这使得链接和检查超级容易。

  • 这样做不是更简单吗: `urls.map(fetch).then(r=>r.json()).catch(error=>new Fail([error,url])` 你得到一个对象数组并且可以通过有关错误和 url 的信息更轻松地挑选出失败的项目。 (2认同)