using a fetch inside another fetch in javascript

Solution 1:

Fetch returns a promise, and you can chain multiple promises, and use the result of the 1st request in the 2nd request, and so on.

This example uses the SpaceX API to get the info of the latest launch, find the rocket's id, and fetch the rocket's info.

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; }

Solution 2:

There is not an issue with nesting fetch() calls. It depends on what you are trying to achieve by nesting the calls.

You can alternatively use .then() to chain the calls. See also How to structure nested Promises

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) 
});

Solution 3:

This is a common question people get tripped up by when starting with Promises, myself included when I began. However, first...

It's great you're trying to use the new Fetch API, but if I were you I would use a XMLHttpRequest implementation for now, like jQuery AJAX or Backbone's overridden implementation of jQuery's .ajax(), if you're already using these libraries. The reason is because the Fetch API is still so new, and therefore experimental at this stage.

With that said, people definitely do use it, but I won't in my own production code until it's out of "experimental" status.

If you decide to continue using fetch, there is a polyfill available. NOTE: you have to jump through extra hoops to get error handling to work properly, and to receive cookies from the server. If you're already loading jQuery, or using Backbone, just stick with those for now; not completely dreadful, anyway.

Now onto code:

You want a flat structure, else you're missing the point of Promises. It's not wise to nest promises, necessarily, because Promises solve what nested async callbacks (callback hell) could not.

You will save yourself time and energy, and produce less buggy code by simply using a more readable code structure. It's not everything, but it's part of the game, so to speak.

Promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel.

-- 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... 
})

Solution 4:

Is it wisely using a code like this in javascript?

Yes. Your code is fine.
Except that after the second request, fetch(anotherUrl).then(function(response) {, I would replace return response.json(); with response.json().then(function(data2) { – just as after the first request.
The variable data2 will then contain the response body of the inner URL request, as desired.
This means that – whatever you want to do with data2, you must do it inside this second callback (since you don't return a promise.)
Also, a few more printouts will help to understand what is happening.

1. The original code – slightly modified

After making those changes, here is a Stack Snippet containing your code: 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; }

which is fine really, although the fat arrow style is more common these days for defining a function.

2. The code refactored

Here is a refactored version of your code. It has an inner chained/nested request – fetch(urlInner) – that depends on data retrieved from a previous/outer request: fetch (urlOuter).
By returning the promises of both the outer and the inner URL fetches, it is possible to access/resolve the promised result later in the code: 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; }

Note that no indentation is deeper than eight spaces.

3. Advantages of this code style

This is clearly a nested style of writing the code – meaning that the chained request fetch(urlInner) is indented and made inside the callback of the first request fetch(urlOuter). Yet, the indentation tree is reasonable, and this style resonates well with my intuition about chaining requests. – But more importantly, this style makes it possible to write error messages that pinpoint which URL failed.

Run the snippet below to see how the error message tells that it is the inner/second URL that causes the error:

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. Flattening all occurrences of .then()?

Inspired by others, you may be tempted to flatten all occurrences of .then(), like below.

I would advise against doing this – or at least think twice before doing it. Why?

  • In the absence of errors, it doesn't matter.
  • If there are errors, such a style will force less distinct error messages:

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; }

The code is nicely flat, but the error caught at the end cannot decide which of the URL requests that failed.


1 All snippets of this answer comply with the JavaScript Semistandard Style.
2 About line 11 – return fetch(urlInner) – it is very easy to forget to return the fetch. (I once forgot it even after writing this answer.) If you do forget it, resultPromise will not contain any promise at all. The last three lines in the snippet will then fail – they will output nothing. The result fails completely!

Solution 5:

I didn't saw an answer with the syntactic sugar of async/await, so I'm posting my answer.

Another way to fetch "inside" another fetch in javascript is like -

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) ;
 }

So actually you call url after url one by one.

This code will work inside async context.