66

I want to get an api and after that call another one. Is it wisely using a code like this in 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)  
});
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
Dvlpr
  • 1,439
  • 1
  • 11
  • 21

8 Answers8

109

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; }
Henke
  • 4,445
  • 3
  • 31
  • 44
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • 1
    Thanks @Henke. Updated to v4 of the spacex api. – Ori Drori May 26 '21 at 09:00
  • I would have preferred a smaller JSON output, but unfortunately I don't have any better suggestions for URLs to use. – Henke May 26 '21 at 09:17
  • 1
    @Henke - good idea. I've changed it to display only the 1st stage property. – Ori Drori May 26 '21 at 09:21
  • What if I need more properties from the first `fetch`? Is it still available in the chained fetch? Why has the `return fetch` one `then` and not 2 `thens`? Where is the `data` object in the second fetch? My use case: get all my repos with some data from the github api and convert the repo update date with another api - `return fetch` - from iso to german date format. So I need the return fetch in a loop through the repos and their properties like date and name. – Timo Dec 29 '21 at 13:08
  • You decide what you want to return, so you can return an array or object containing previous responses and new / updated data. However, async/await would be better for your use case. Open another questions with the info, and what you've tried. – Ori Drori Dec 29 '21 at 13:13
  • Can I put two `catches`, one for the outer and the other for the inner fetch to see what went wrong? And I would remove the result variable and instead chain the last `then` at the end. – Timo Jan 26 '22 at 08:24
  • instead of `then(response => response.json())` I had to `then(response => return response.json())` – Timo Jan 26 '22 at 08:32
  • 1
    @Timo - You can chain multiple catch and then block - see this [article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch#using_and_chaining_the_catch_method). Your `then(response => return response.json())` is a syntax error. Arrow functions return automatically. You probably have curly brackets, and that's why you need to return. – Ori Drori Jan 26 '22 at 17:13
30

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) 
});
Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
6

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... 
})
jeremiah.trein
  • 776
  • 6
  • 6
  • 3
    How the data is being send from one promise chain to the next will be helpful for readers. Otherwise great post. – Si8 Nov 04 '19 at 21:13
  • I partly agree with @Si8 but cannot see any valuable information regarding the problem here with `fetch`. – Timo Dec 28 '21 at 20:05
4

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.

Tamir Adler
  • 341
  • 2
  • 14
  • how could i implement promise.all to return the data of both responses? – benwl Jul 20 '22 at 22:35
  • You can't as the second promise is depends on the first promise response. But if it was not depends you could do something like - const [firstResponse, secondResponse] = await Promise.all([promise1, promise2, promise3]) – Tamir Adler Aug 01 '22 at 18:06
3

I would use either an array of fetches instead or an array of urls, both in the order you would like to execute them. Then use reduce to execute them in sequence. This way it is much more scalable.

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);
Robert-Jan Kuyper
  • 2,418
  • 12
  • 22
1

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!

Henke
  • 4,445
  • 3
  • 31
  • 44
1

just some ways of doing it.

1, using async -await

 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, chaining of 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, chaining in a better way

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

you decide.

Safnas
  • 144
  • 1
  • 11
-1

I suggest using axios, is much better and you don't have to deal with JSON format. Also, the code looks cleaner and is easier to understand.

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

Blockquote from LogRocket.com:

As with Fetch, Axios is promise-based. However, it provides a more powerful and flexible feature set.

Advantages of using Axios over the native Fetch API include:

  • Request and response interception
  • Streamlined error handling
  • Protection against XSRF
  • Support for upload progress
  • Response timeout
  • The ability to cancel requests
  • Support for older browsers
  • Automatic JSON data transformation
Gass
  • 7,536
  • 3
  • 37
  • 41