Reputation: 1622
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)
});
Upvotes: 72
Views: 96799
Reputation: 153
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.
Upvotes: 1
Reputation: 3306
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);
Upvotes: 3
Reputation: 9344
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 */ });
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
Upvotes: -1
Reputation: 421
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.
Upvotes: 6
Reputation: 5767
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.
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.
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.
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; }
.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?
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!
Upvotes: 1
Reputation: 191946
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; }
Upvotes: 115
Reputation: 806
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.
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...
})
Upvotes: 6
Reputation: 1
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)
});
Upvotes: 31