在JavaScript中嵌套使用fetch:如何在一个fetch内部使用另一个fetch

67
我想获取一个API,然后调用另一个API。在JavaScript中使用这样的代码明智吗?
fetch(url, {
 method: 'get',
 }).then(function(response) {  
  response.json().then(function(data) {  
    fetch(anotherUrl).then(function(response) {
      return response.json();
    }).catch(function() {
      console.log("Booo");
    });
  });  
}) 
.catch(function(error) {  
  console.log('Request failed', error)  
});

4
你想达到什么目的?第二个调用是否依赖于第一个调用的响应数据? - guest271314
2
@guest271314 是的,第二个调用取决于第一个调用的响应数据。 - Dvlpr
你也可以使用.then()链接调用。 - guest271314
1
如果你不从每个 Promise(then)回调中 return,那么你的最终 .catch() 就无法工作。 - Bergi
8个回答

109

Fetch 方法返回一个 Promise 对象,你可以使用链式调用多个Promise,并在第二个请求中使用第一个请求的结果等。

此示例使用SpaceX API获取最新发射信息,查找火箭的ID,并获取火箭的信息。

const url = 'https://api.spacexdata.com/v4';

const result = fetch(`${url}/launches/latest`, { method: 'get' })
  .then(response => response.json()) // pass the data as promise to next then block
  .then(data => {
    const rocketId = data.rocket;

    console.log(rocketId, '\n');
  
    return fetch(`${url}/rockets/${rocketId}`); // make a 2nd request and return a promise
  })
  .then(response => response.json())
  .catch(err => {
    console.error('Request failed', err)
  })

// I'm using the result const to show that you can continue to extend the chain from the returned promise
result.then(r => {
  console.log(r.first_stage); // 2nd request result first_stage property
});
.as-console-wrapper { max-height: 100% !important; top: 0; }


1
感谢 @Henke。已更新至 SpaceX API 的 v4 版本。 - Ori Drori
1
@Henke - 好主意。我已经更改它,只显示第一个阶段的属性。 - Ori Drori
如果我需要从第一个fetch获取更多属性,它是否仍然可以在链接的fetch中使用?为什么return fetch只有一个then而不是两个thens?第二个fetch中的data对象在哪里?我的用例:使用Github API获取所有存储库及其某些数据,并使用另一个API将存储库更新日期从ISO转换为德国日期格式 - return fetch。因此,我需要在循环中通过存储库及其属性(如日期和名称)使用return fetch - Timo
我不得不使用then(response => return response.json())代替then(response => response.json()) - Timo
1
@Timo - 你可以链接多个catch和then块 - 参见这篇文章。你的then(response => return response.json())是语法错误。箭头函数会自动返回。你可能有花括号,所以需要return。 - Ori Drori
显示剩余3条评论

30

嵌套fetch()调用本身没有问题,这取决于您希望通过嵌套调用实现什么目的。

您还可以使用.then()来链接这些调用。另请参见如何构造嵌套的Promise

fetch(url)
.then(function(response) { 
  return response.json()
})
.then(function(data) {   
  // do stuff with `data`, call second `fetch`
  return fetch(data.anotherUrl)
})
.then(function(response) { 
  return response.json(); 
})
.then(function(data) {
  // do stuff with `data`
})
.catch(function(error) { 
  console.log('Requestfailed', error) 
});

6

当人们开始使用Promise时,这是一个常见的问题,包括我自己刚开始学习时也遇到了。然而,首先...

很好,你正在尝试使用新的Fetch API,但如果我是你,我会暂时使用XMLHttpRequest实现,比如jQuery AJAX或Backbone覆盖了jQuery的.ajax()实现,如果您已经在使用这些库。原因是Fetch API仍然非常新,因此在这个阶段仍处于试验阶段。

话虽如此,人们确实在使用它,但在“试验”状态下之前,我不会在自己的生产代码中使用它。

如果您决定继续使用fetch,则有一个polyfill可用。注意:您必须跳过额外的步骤才能使错误处理正常工作,并从服务器接收cookie。如果您已经加载了jQuery或使用Backbone,请继续使用它们;无论如何都不完全糟糕。

现在进入代码部分:

您需要一个平坦的结构,否则您将错过Promises的要点。不一定明智地嵌套Promises,因为Promises解决了嵌套的异步回调(回调地狱)无法解决的问题。

通过简单使用更易读的代码结构,您将节省时间和精力,并生成更少错误的代码。这并不是全部,但它是游戏的一部分,可以说。

Promise是关于使异步代码保留大多数同步代码丢失的属性,例如平坦缩进和一个异常通道。

-- Petka Antonov (Bluebird Promise Library)

// run async #1
asyncGetFn()
// first 'then' - execute more async code as an arg, or just accept results
// and do some other ops
.then(response => {
    // ...operate on response data...or pass data onto next promise, if needed
})
// run async #2
.then(asyncGetAnotherFn)
.then(response => {
    // ...operate on response data...or pass data onto next promise, if needed
})
// flat promise chain, followed by 'catch'
// this is sexy error handling for every 'then' above
.catch(err => {  
  console.error('Request failed', err) 
  // ...raise exeption...
  // ... or, retry promise... 
})

3
如何将数据从一个 Promise 链传递到下一个对读者来说会很有帮助。否则,这篇文章很棒。 - Si8
我在某种程度上同意@Si8的观点,但是在这里关于“fetch”问题方面没有看到任何有价值的信息。 - Timo

4

我没有看到使用async/await语法糖的答案,所以我要发表我的答案。

在JavaScript中,另一种获取“内部”获取(fetch)的方式是 -

try {
    const response = await fetch(url, {method: 'get'});
    const data = response.json();
    //use the data...
    const anotherResponse = await fetch(url, {method: 'get'});
    const anotherdata = anotherResponse.json();
    //use the anotherdata...
 } catch (error) {
    console.log('Request failed', error) ;
 }

实际上,您需要一个接一个地调用URL。

此代码将在异步上下文中运行。


我该如何实现Promise.all来返回两个响应的数据? - benwl
你不能这样做,因为第二个 Promise 是依赖于第一个 Promise 的响应结果的。但是如果不是依赖关系,你可以这样写: const [firstResponse, secondResponse] = await Promise.all([promise1, promise2, promise3])。 - Tamir Adler

3
我会使用一系列的fetch或者URL数组,以你想要执行的顺序排列。然后使用reduce按顺序依次执行它们。这样做可以使其更具可扩展性。

const urls = [
  'https://api.spacexdata.com/v4/launches/latest',
  'https://api.spacexdata.com/v4/launches/latest',
  'https://api.spacexdata.com/v4/launches/latest'
];

// handle the fetch logic
// and handle errors
const handleFetch = async (url) => {
  const resp = await fetch(url).catch(console.error);
  return resp.json()
}

// reduce fetches, receives the response
// of the previous, log it (and maybe use it as input)
const reduceFetch = async (acc, curr) => {
  const prev = await acc;
  console.log('previous call:', prev);

  return handleFetch(curr);
}

const pipeFetch = async urls => urls.reduce(reduceFetch, Promise.resolve(''));

pipeFetch(urls).then(console.log);


1

像这样在javascript中使用代码明智吗?

是的。你的代码没问题。
除了第二个请求之后,fetch(anotherUrl).then(function(response) {, 我会用response.json().then(function(data2) {替换return response.json(); - 就像第一个请求之后一样。
变量data2将包含所需的内部URL请求的响应主体。
这意味着-无论你想用data2做什么,你必须在这个第二个回调函数中完成(因为你没有返回一个promise)。
此外,更多的输出将有助于理解正在发生的事情。

1. 原始代码-稍作修改

在进行这些更改后,以下是包含你的代码的Stack Snippet:1

const url = 'https://jsonplaceholder.typicode.com/todos/1';
const anotherUrl = 'https://jsonplaceholder.typicode.com/todos/4';
fetch(url, {
  method: 'get'
}).then(function (response) {
  response.json().then(function (data) {
    console.log('Response body of outer "url":');
    console.log(JSON.stringify(data) + '\n\n');
    fetch(anotherUrl).then(function (response) {
      response.json().then(function (data2) {
        console.log('Response body of inner "anotherUrl":');
        console.log(JSON.stringify(data2) + '\n\n');
      });
    }).catch(function () {
      console.log('Booo');
    });
  });
})
.catch(function (error) {
  console.log('Request failed', error);
});
.as-console-wrapper { max-height: 100% !important; top: 0; }

这样也可以,不过现在更常见的是使用箭头函数来定义函数。

2. 重构后的代码

这是您代码的重构版本。 它有一个内部的链式/嵌套请求- fetch(urlInner) - 依赖于从前一个/外部请求检索到的数据:fetch(urlOuter)
通过返回外部和内部URL获取的Promise, 可以在以后的代码中访问/解决承诺的结果: 2

const urlOuter = 'https://jsonplaceholder.typicode.com/todos/1';
let urlInner = '';
const resultPromise = fetch(urlOuter)
  .then(responseO => responseO.json())
  .then(responseBodyO => {
    console.log('The response body of the outer request:');
    console.log(JSON.stringify(responseBodyO) + '\n\n');
    const neededValue = responseBodyO.id + 3;
    urlInner = 'https://jsonplaceholder.typicode.com/todos/' + neededValue;
    console.log('neededValue=' + neededValue + ', URL=' + urlInner);
    return fetch(urlInner)
      .then(responseI => responseI.json())
      .then(responseBodyI => {
        console.log('The response body of the inner/nested request:');
        console.log(JSON.stringify(responseBodyI) + '\n\n');
        return responseBodyI;
      }).catch(err => {
        console.error('Failed to fetch - ' + urlInner);
        console.error(err);
      });
  }).catch(err => {
    console.error('Failed to fetch - ' + urlOuter);
    console.error(err);
  });

resultPromise.then(jsonResult => {
  console.log('Result - the title is "' + jsonResult.title + '".');
});
.as-console-wrapper { max-height: 100% !important; top: 0; }

请注意,缩进不超过八个空格。
3. 这种代码风格的优点
显然,这是一种嵌套式的编写代码的方式——意味着链接请求fetch(urlInner)被缩进并在第一个请求fetch(urlOuter)的回调函数中完成。然而,缩进树是合理的,并且这种风格与我的关于链接请求的直觉相吻合。但更重要的是,这种风格使得编写能够准确定位哪个URL失败的错误消息成为可能。
运行下面的片段,看看错误消息如何指出是“内部/第二个URL”导致了错误:

const urlOuter = 'https://jsonplaceholder.typicode.com/todos/1';
let urlInner = '';
const resultPromise = fetch(urlOuter)
  .then(responseO => responseO.json())
  .then(responseBodyO => {
    console.log('The response body of the outer request:');
    console.log(JSON.stringify(responseBodyO) + '\n\n');
    const neededValue = responseBodyO.id + 3;
    urlInner = 'https://VERY-BAD-URL.typicode.com/todos/' + neededValue;
    console.log('neededValue=' + neededValue + ', URL=' + urlInner);
    return fetch(urlInner)
      .then(responseI => responseI.json())
      .then(responseBodyI => {
        console.log('The response body of the inner/nested request:');
        console.log(JSON.stringify(responseBodyI) + '\n\n');
        return responseBodyI;
      }).catch(err => {
        console.error('Failed to fetch - ' + urlInner);
        console.error(err);
      });
  }).catch(err => {
    console.error('Failed to fetch - ' + urlOuter);
    console.error(err);
  });

resultPromise.then(jsonResult => {
  console.log('Result - the title is "' + jsonResult.title + '".');
});
.as-console-wrapper { max-height: 100% !important; top: 0; }

4. 将所有的.then()展开?

受他人启发,你可能会想将所有的.then()展开成下面这样。

我建议不要这样做,或者至少在这样做之前三思。为什么?

  • 如果没有错误,这样做并没有什么影响。
  • 如果出现错误,则这种风格将强制较少的不同错误消息:

const urlOuter = 'https://jsonplaceholder.typicode.com/todos/1';
let urlInner = '';
const resultPromise = fetch(urlOuter)
  .then(responseO => responseO.json())
  .then(responseBodyO => {
    console.log('The response body of the outer request:');
    console.log(JSON.stringify(responseBodyO) + '\n\n');
    const neededValue = responseBodyO.id + 3;
    urlInner = 'https://VERY-BAD-URL.typicode.com/todos/' + neededValue;
    console.log('neededValue=' + neededValue + ', URL=' + urlInner);
    return fetch(urlInner);
  })
  .then(responseI => responseI.json())
  .then(responseBodyI => {
    console.log('The response body of the inner/nested request:');
    console.log(JSON.stringify(responseBodyI) + '\n\n');
    return responseBodyI;
  }).catch(err => {
    console.error('Failed to fetch one or more of these URLs:');
    console.log(urlOuter);
    console.log(urlInner);
    console.log(err);
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

代码结构很简洁,但最后捕获的错误无法确定是哪个URL请求失败了。

1 本答案的所有代码块都符合JavaScript Semistandard Style
2 关于第11行的代码 return fetch(urlInner),很容易忘记加上 return。我曾经在写完这个答案后也忘了加上它。如果你确实忘了加上它,resultPromise 将不会包含任何 promise。代码块的最后三行将无法执行,输出结果将为空。整个结果将会失败!


1

以下是几种处理方法。

1、使用异步-等待

 app.get("/getemployeedetails/:id", async (req, res) => {
  const id = req.params.id;
  const employeeUrl = "http://localhost:3000/employee/" + id;
  try {
    const response = await fetch(employeeUrl);
    const employee = await response.json();

    const projectUrl = "http://localhost:3000/project/" + employee.project_id;
    const response1 = await fetch(projectUrl);
    const project = await response1.json();

    const result = {
      ...employee,
      ...project,
    };
    res.send(result);
  } catch (error) {
    console.log("getData: ", error);
  }
});

2、then的链式调用

app.get("/getemployeedetails/:id", (req, res) => {
  const id = req.params.id;
  const employeeUrl = "http://localhost:3000/employee/" + id;
  let employeeResponse = null;
  fetch(employeeUrl)
    .then((employee) => employee.json())
    .then((resp) => {
        employeeResponse = resp
      const projectUrl =
        "http://localhost:3000/project/" + employeeResponse.project_id;
      return fetch(projectUrl);
    })
    .then((project) => project.json())
    .then((projectResponse) => {
      const result = {
        ...employeeResponse,
        ...projectResponse,
      };
      res.send(result);
    })
    .catch((err) => console.log(err));
});

3、更好的链式调用

app.get("/getemployeedetails/:id", (req, res) => {
  const id = req.params.id;
  getEmployeeResponse(id).then((employeeResponse) => {
    getProjectResponse(employeeResponse.project_id)
      .then((projectResponse) => {
        const result = {
          ...employeeResponse,
          ...projectResponse,
        };
        res.send(result);
      })
      .catch((err) => console.log(err));
  });
});

function getEmployeeResponse(id) {
  return new Promise((resolve, reject) => {
    const employeeUrl = "http://localhost:3000/employee/" + id;
    fetch(employeeUrl)
      .then((employee) => employee.json())
      .then((resp) => resolve(resp))
      .catch((err) => reject(err));
  });
}

function getProjectResponse(id) {
  return new Promise((resolve, reject) => {
    const projectUrl = "http://localhost:3000/project/" + id;
    fetch(projectUrl)
      .then((project) => project.json())
      .then((resp) => resolve(resp))
      .catch((err) => reject(err));
  });
}

你做决定。

-1

我建议使用axios,它更好用,而且你不必处理JSON格式。此外,代码看起来更清晰,更易于理解。

axios.get(firstUrl).then((resp1) => {
   // Handle success of first api
   axios.get(secondUrl).then((resp2) => {
      return resp2.data                 
   }).catch((error) => { /* Handle error of second api */ });
}).catch((error) => { /* Handle error of first api */ });

来自LogRocket.com的引用:

Axios和Fetch一样基于promise。但是,它提供了更强大和灵活的功能集。

使用Axios而不是本机Fetch API的优势包括:

  • 请求和响应拦截
  • 流畅的错误处理
  • 防XSRF攻击
  • 支持上传进度
  • 响应超时
  • 取消请求的能力
  • 支持旧版浏览器
  • 自动JSON数据转换

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接