I have a wrapper function for the fetch api to fetch different endpoints to my api but somehow it keeps complaining that there is unhandled rejection TypeError: Cannot read property 'catch' of undefined
const apiRequest = (url) => {
return fetch()
.then(async resp =>{
const json = await resp.json()
if(json.status == "success") return json
return Promise.reject('err')
})
.catch(err => {
return Promise.reject(err)
})
}
calling the function like:
apiRequest('/test')
.then(data => console.log(data))
.catch(err => console.log(err))
what am I doing wrong?
Note: When this answer was posted, the question didn't have the return. The OP added it afterward, claiming it was in their original code. But they also accepted the answer, so something below must have helped... :-)
apiRequest needs to return the promise chain. Right now, it doesn't, so calling it returns undefined.
const apiRequest = (url) => {
return fetch(url) // *** Note: Added `url` here
// ^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
.then(async resp =>{
const json = await resp.json()
if(json.status == "success") return json
return Promise.reject('err')
})
.catch(err => {
return Promise.reject(err)
})
}
But, three things:
There's no point whatsoever to that catch handler; and
You need to check for response success before calling json; and
There's no reason for that then handler to be an async function.
Instead:
const apiRequest = (url) => {
return fetch(url) // *** Note: Added `url` here
.then(resp => {
if (!resp.ok) {
throw new Error("HTTP status " + resp.status);
}
return resp.json();
})
};
(I've also added some missing semicolons there, but if you prefer to rely on ASI, just leave them off.)
If the fetch promise is rejected, that rejection will be carried through to the promise from apiRequest for the caller to handle. If the fetch promise is fulfilled but resp.ok is false (because there was an HTTP level error like a 404 or 500), the promise from apiRequest will be rejected with the error thrown in the then handler. If those things work but the json call fails, the promise from apiRequest will be rejected with the error from json. Otherwise, the promise from apiRequest will be fulfilled with the parsed data.
It can also be a concise form arrow function if you prefer:
const apiRequest = (url) => fetch(url).then(resp => { // *** Note: Added `url` to `fetch` call
if (!resp.ok) {
throw new Error("HTTP status " + resp.status);
}
return resp.json();
});
Your original code used an async function in then. If you can ues async functions in your environment, you may prefer to make apiRequest an async function:
const apiRequest = async (url) => {
const resp = await fetch(url); // *** Note: Added `url` here
if (!resp.ok) {
throw new Error("HTTP status " + resp.status);
}
return resp.json();
};
Related
Hello: i am trying to do a fetch to an endpoint in a react component. The fetch i have is working (all console logs show it is working). since this a function so i can reuse it - i want to return something from it. i tried returning the response but i dont think i have my return in the right place or something else is wrong. here is the log i am referring to:
console.log(response)
and here is the full code:
const doFetch = (fetchUri) => {
fetch(fetchUri)
.then(async response => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
console.log('running fetch')
console.log(response)
console.log('showing data from fetch')
console.log(data)
return(response)
// this.setState({ totalReactPackages: data.total })
})
.catch(error => {
this.setState({ errorMessage: error.toString() });
console.error('There was an error!', error);
});
};
So I am hoping someone can help me do that 'return' properly. Thanks! Gerard
you did returned properly inside fetch but you did not returned fetch itself :)
const doFetch = (fetchUri) => {
return fetch(fetchUri)
.then(async response => {
const data = await response.json();
// check for error response
if (!response.ok) {
// get error message from body or default to response statusText
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
console.log('running fetch')
console.log(response)
console.log('showing data from fetch')
console.log(data)
return (response)
// this.setState({ totalReactPackages: data.total })
})
.catch(error => {
this.setState({errorMessage: error.toString()});
console.error('There was an error!', error);
});
};
doFetch('randomurl').then(parsedAndPreparedResponse => this.setState({ data: parsedAndPreparedResponse }))
As Michal pointed out in his answer, you're only returning within the fetch, not from doFetch itself.
But to extend a little
The async/await within the then() is unnecessary since no asynchronous call is being made within it. The callback in then() will be run when the fetch promise is resolved.
So either get rid of the async/await or change your code to only use async/await.
If you do it could look something like this:
const doFetch = async fetchUri => {
try {
const response = await fetch(fetchUri)
// check for error response
if (!response.ok) {
throw new Error({
status: response.status,
statusText: response.statusText
});
}
// On successful call
const data = response.json();
// Return the data
return data();
} catch (error) {
// Error handling
}
};
You can call the function like this:
// Using the await keyword
const response = await doFetch(/* your fetchUri */);
// Using .then()
// The callback function in then() will run when the promise from
// doFetch() is resolved
doFetch(/* your fetchUri */).then(res => {
// Whatever you want to do with the response
});
Remember that in order you use the await keyword you need to be inside an async function.
const anAsyncFunction = async () => {
const response = await doFetch(/* your fetchUri */);
}
Coming from Jquery $.get, I am trying to rewrite a function that gets data in a json. So far, this is what I do:
async function get_highlights(){
const mirespuesta = await fetch("/api/v1/gethighlights/"+network_number)
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then(json => {
console.log(json) // --> here, I get the correct dara
return json;
})
.catch(function () {
this.dataError = true;
})
return mirespuesta
} // but I don't get that data returned
But I just get this when I call the fuction:
that has the values, but I can't get them returned or used on the main scope of my application.
What am I doing wrong?
The problem is that you never return the promise chain from your function. So your async function's return value is a promise fulfilled with undefined, just like a non-async function with no return returns undefined.
There's another issue with that code: If you're using an async function, generally it's best to use await rather than .then, .catch, etc. Also, don't catch rejection in the function; instead, let the caller handle it so it knows something went wrong.
So for instance:
async function get_highlights() {
const response = await fetch("/api/v1/gethighlights/"+network_number);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.json();
}
But if you really want to handle errors inline:
async function get_highlights() {
const response = await fetch("/api/v1/gethighlights/"+network_number);
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
try {
return await response.json();
} catch { // Note: ES2019 allows optional `catch` bindings
this.dataError = true;
}
}
but again I don't recommend that, because the caller has to check to see whether they got data or undefined when the promise is fulfilled (or check dataError, I guess :-) ).
Side note: Note that in the first example, which doesn't have a try/catch, I did return response.json(); but in the second example, where the return is inside a try/catch, I did return await respohnse.json();. The reason is that when returning a promise at the top level of the function, there's no effective difference between return somePromise and return await somePromise, but that's not true if the code is in a control block like a try/catch. Having the await ensures that our catch block is executed if the promise is rejected. Not having it wouldn't ensure that.
This is what your function should look like:
async function get_highlights() {
const mirespuesta = await fetch("/api/v1/gethighlights/"+network_number);
if (!mirespuesta.ok) {
throw new Error("HTTP error " + mirespuesta.status);
}
return mirespuesta.json();
}
But, most importantly this is how it should be used:
const mydata = await get_highlights();
An async function ALWAYS returns a promise, and your question probably becomes a dupe of this question
Another way would be to use just await twice
try {
const mirespuesta = await (await fetch("/api/v1/gethighlights/"+network_number)).json();
} catch {
throw new Error(response.status);
}
return mirespuesta;
I want to call this api multiple times in my project and when I am calling it , It continues giving an error which is
TypeError: Failed to execute 'json' on 'Response': body stream already
read at main.js:Line number
My Code is as Follows
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
a.then((fetchdata) => {
return fetchdata.json()
}).then((readingData) => {
console.log(readingData)
}).catch((err) => {
console.log(err)
})
You're not calling fetch multiple times. You're calling it once, and then trying to read the response body multiple times. That's why the error says you're trying to read the body when the stream is already closed — it was closed when you were done reading it the first time.
If you want to use the data twice, store it somewhere and use it twice.
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
// **************** Use it twice here
}).catch((error) => {
console.log(error)
})
If you want to fetch it again because it may have been updated, call fetch again:
let thisIsUrl = 'https://api.covid19api.com/summary';
fetch(thisIsUrl)
.then((data) => {
return data.json();
}).then((apidata) => {
console.log(apidata);
return apidata;
}).catch((error) => {
console.log(error);
});
// Presumably this is later (in time), not immediately after the above
fetch(thisIsUrl)
.then((fetchdata) => {
return fetchdata.json();
}).then((readingData) => {
console.log(readingData);
}).catch((err) => {
console.log(err);
});
Finally, this seems unlikely, but if you really want to fetch it once and use that one result in multiple places via the promise chain, keep the promise from then rather than the promise from fetch:
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
});
a.then((apidata) => {
// ***** Use it here
}).catch((error) => {
console.log(error)
})
a.then((readingData) => {
// ***** And then again here
}).catch((err) => {
console.log(err);
});
Side note: Your code is falling prey to a footgun in the fetch API; I've written about it in this blog post. fetch only rejects its promise on network errors, not HTTP errors. You have to check for those yourself in the first fulfillment handler, by checking for ok on the response object:
fetch("/your/resource")
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status); // Or better, use an Error subclass
}
return response.json();
})
// ...
fetch returns Promise, generally, promises have something like state inside themself;
pending: initial state, neither fulfilled nor rejected.
fulfilled: meaning that the operation was completed successfully.
rejected: meaning that the operation failed.
(source)
So when we call them and get the value from them with then, catch and etc. then they change the state after that call. So here, when you read the value with a.then(…, the promise changes its state to fulfilled and you are not able to call it again, you need a new and fresh Promise, actually a new instance of the fetch.
I want to recommend you to use Promise.all().
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
Promise.all([a,a,a]);
.then(results => {
// Results are here.
});
i'm working with an API that allows me to sync data to a local DB. There is a syncReady API that I'm calling recursively until the sync batch is ready to start sending data. The recursion is working correctly and the .then callback is called, but the resolve function never resolves the response.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
return new Promise((res, rej) => {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
res(response);
}, (error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
this.syncReady(token, batchID);
} else rej(error);
}
);
});
};
I get the 497 logged and the "The Response: {"pagesTotal";0}" response but the res(response) never sends the response down the chain. I've added a console.log message along the entire chain and none of the .then functions back down the chain are firing.
I hope I've explained this well enough :-). Any ideas why the promise isn't resolving?
Thanks!
First, you don't need to wrap something that returns a promise with a new Promise. Second, for your error case you don't resolve the promise if it is 497.
const request = require('request-promise');
const config = require('../Configs/config.json');
function Sync(){}
Sync.prototype.syncReady = function (token, batchID) {
config.headers.Get.authorization = `bearer ${token}`;
config.properties.SyncPrep.id = batchID;
return request({url: config.url.SyncReady, method: config.Method.Get, headers: config.headers.Get, qs: config.properties.SyncPrep})
.then((response) => {
console.log(`The Response: ${response}`);
return response;
})
.catch((error) => {
console.log(error.statusCode);
if(error.statusCode === 497){
return this.syncReady(token, batchID);
} else {
throw error;
}
})
);
};
Maybe something like the above will work for you instead. Maybe try the above instead. As a general rule of thumb, it's you almost always want to return a Promise.
This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 6 years ago.
I have the following promise chain:
return fetch(request)
.then(checkStatus)
.then(response => response.json())
.then(json => ({ response: json }))
.catch(error => ({ error }))
Where checkstatus() checks if the request was successful, and returns an error if it wasn't. This error will be caught and returned. But, the problem is that I want to add the both response.statusText and the results of response.json() to the error. The problem is that when I parse it I lose the original response in the chain since I have to return response.json() because it's a promise.
This is what checkstatus does currently:
const checkStatus = response => {
if (response.ok) return response
const error = new Error('Response is not ok')
// this works because the response hasn't been parsed yet
if (response.statusText) error.message = response.statusText
// an error response from our api will include errors, but these are
// not available here since response.json() hasn't been called
if (response.errors) error.errors = response.errors
throw error
}
export default checkStatus
How do I return an error with error.message = response.statusText and error.errors = response.json().errors?
Here's my helper for sane fetch error handling, fetchOk:
let fetchOk = (...args) => fetch(...args)
.then(res => res.ok ? res : res.json().then(data => {
throw Object.assign(new Error(data.error_message), {name: res.statusText});
}));
Which I then substitute for fetch.
let fetchOk = (...args) => fetch(...args)
.then(res => res.ok ? res : res.json().then(data => {
throw Object.assign(new Error(data.error_message), {name: res.statusText});
}));
fetchOk("https://api.stackexchange.com/2.2/blah")
.then(response => response.json())
.catch(e => console.log(e)); // Bad Request: no method found with this name
var console = { log: msg => div.innerHTML += msg + "<br>" };
<div id="div"></div>
It doesn't load the data unless there's an error, making it a direct replacement.
I use the new async/await syntax, since that reads in a more intuitive way:
async fetchData(request) {
try {
const response = await fetch(request)
const data = await response.json()
// return the data if the response was ok
if (response.ok) return { data }
// otherwise return an error with the error data
const error = new Error(response.statusText)
if (data.errors) error.errors = data.errors
throw error
} catch (error) {
return { error }
}
}
It makes it very easy to handle both the promise that fetch returns as well as the promise that response.json() returns.