I have this piece of code that calls a function getTableData and expects a Promise in return.
function populateTableRows(url) {
successCallback = () => { ... };
errorCallback = () => { ... };
getTableData(url, successCallback, errorCallback).then(tableData => {
// do stuff with tableData
}
}
This is used in many places across my codebase, and I'm looking to keep the behavior the same as I move away from using jQuery's ajax (and jQuery in general)
In getTableData, I'm currently using $.ajax like so
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
const ajaxOptions = {
type: 'POST',
url: url,
dataType: 'json',
xhrFields: {
withCredentials: true
},
crossDomain: true,
data: { // some data }
};
return $.ajax(ajaxOptions).done(successCallback).fail(errorCallback);
}
This currently returns a Promise for successful requests. For bad requests where fail is invoked, it doesn't appear that a Promise is returned and the then doesn't run in the calling function (which is okay in this case).
When converting the request over to use fetch, I have something like this
function getTableData(url, successCallback, errorCallback) {
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials: 'include',
body: { // some data }
})
.then(response => {
let json = response.json();
if (response.status >= 200 && response.status < 300) {
successCallback(json);
return json;
} else {
return json.then(error => {throw error;});
}
}).catch((error) => {
errorCallback(error);
return
});
Successful requests appear to be behaving similarly to the ajax code that I currently have, but now the then callback is running for bad requests which is causing errors in my code.
Is there a way with fetch to mimic the fail behavior of jQuery where the Promise is seemingly aborted for bad requests? I'm fairly new to using Promises and after some experimentation/searching I haven't been able to come up with a solution.
When you .catch() in a chain of promises, it means you already handled the error, and subsequent .then() calls continue successfully.
For example:
apiCall()
.catch((error) => {
console.log(error);
return true; // error handled, returning true here means the promise chain can continue
})
.then(() => {
console.log('still executing if the API call fails');
});
What you want, in your case, is when you handle the error with the callback, to continue to throw it so the promise chain is broken. The chain then further needs a new .catch() block to handle the new error.
apiCall()
.catch((error) => {
console.log(error); // "handled", but we're still not done
throw error; // instead of returning true, we throw the error further
// 👆 this can also be written as `return Promise.reject(error);`
})
.then(() => {
console.log('not executing anymore if the API call fails');
})
.catch((error) => {
// handle the same error we have thrown from the previous catch block
return true; // not throwing anymore, so error is handled
})
.then(() => {
console.log('always executing, since we returned true in the last catch block');
});
By the way, what you return from one then/catch block, the following one will get it as a param.
apiCall()
.then((response) => {
/* do something with response */;
return 1;
})
.catch((error) => { return 'a'; })
.then((x) => console.log(x)) // x is 'a' if there's an error in the API call, or `1` otherwise
In your .catch you implicitly return undefined and thus "handle" the error. The result is a new Promise that fulfills to undefined.
.catch((error) => {
errorCallback(error);
return Promise.reject();
});
should be enough to keep the returned Promise rejecting.
Or you assign the intermediate Promise to a var and return that, and not the result to the fail handling:
var reqPromise = fetch(url, {
// ...
})
.then(response => {
// ...
return json.then(error => {throw error;});
});
reqPromise.catch((error) => {
errorCallback(error);
return
});
return reqPromise;
Related
I am sorry if it is simple question, I'm new to javascript
So I have simple axios GET request. It is used three times in my code, so I thought that I could make it an external function, to avoid duplicating code, to making it cleaner and easy readable
The problem is when I call to that function, return value is undefined. And this is because code is working like synchronous. So I thought that I need to make the function return a Promise, and in function call I have to use async/await or then syntax to get the response in the right time. But after many tries code is still running as synchronous
I read a lot of theory on promises and how they work, got a solid understanding of when they change states, but something goes wrong when I try to implement them on my own
Here is the function that retrieves data
const getMessagesFromChat = async (JWT_header, chatId) => {
if (JWT_header !== '') {
//1 let messages
//4 return
axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
})
.then(response => {
console.log('messages (fetch)', response.data)
//1 messages = response.data
//1 return messages
return response.data //2
//3 return Promise.resolve(response.data)
})
.catch(error => {
console.log(error, error.response)
})
//5 return Promise.resolve(messages)
}
}
I marked comments with numbers based on what I've tried
make a variable and return it in then block
make function async so everything it returns is wrapped in promise
return explicit promise using Promise.resolve() in then block
return the whole request as a promise
return explicit promise using Promise.resolve() after the request
All responses except 4 were undefined. In 4 variant log statement shows the promise object itself instead of promise value. In other variants log statement shows first `undefined` response and then the actual response from request
I tried to implement second advice from
https://stackoverflow.com/questions/45620694/how-to-return-response-of-axios-in-return/45622080
but it did not work.
This is the function I tried to use to get the result (onClick)##
const selectChat = () => {
const JWT_header = getToken()
if (JWT_header !== null) {
try {
const messages = await getMessagesFromChat(JWT_header, chatId)
console.log('messages (after fetch)', messages)
//setMessages(messages)
} catch (error) {
console.log(error)
}
} else {
setIsLoggedIn(false)
}
or
const selectChat = () => {
const JWT_header = getToken()
if (JWT_header !== null) {
getMessagesFromChat(JWT_header, chatId)
.then(response => {
console.log(response)
setMessages(response)
})
.catch (error =>console.log(error))
} else {
setIsLoggedIn(false)
}
But none of them worked as expected
What am I doing wrong?
Your function doesn't return anything. A return statement in a then callback is not sufficient, you'd still have to return the promise chain itself from the outer function.
Use either .then() syntax:
function getMessagesFromChat(JWT_header, chatId) { // no `async` here
if (JWT_header !== '') {
return axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
// ^^^^^^
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
}).then(response => {
console.log('messages (fetch)', response.data)
return response.data
})
} else {
return Promise.resolve(undefined)
// ^^^^^^ important for chaining
}
}
or async/await syntax:
async function getMessagesFromChat(JWT_header, chatId) {
if (JWT_header !== '') {
const respnse = await axios.get(`${SECURED_API_PATH}/messages/chat/${chatId}`, {
// ^^^^^
headers: {authorization: JWT_header},
params: {size: 80, page: 0}
});
console.log('messages (fetch)', response.data)
return response.data
}
// else return undefined
}
I have an API on backend which sends status 201 in case of a successful call and if there's
any error with the submitted data it sends status 422 (Unprocessable Entity) with a json response like below:
{
"error": "Some error text here explaining the error"
}
Additionally it sends 404 in case API fails to work on back end for some reason.
On the front-end I'm using the below code to fetch the response and perform a success or error callback based on response status code:
fetch("api_url_here", {
method: 'some_method_here',
credentials: "same-origin",
headers: {
"Content-type": "application/json; charset=UTF-8",
'X-CSRF-Token': "some_token_here"
}
})
.then(checkStatus)
.then(function json(response) {
return response.json()
})
.then(function(resp){
successCallback(resp)
})
.catch(function(error){
errorCallback(error);
});
//status function used above
checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
return Promise.reject(new Error(response))
}
}
The successCallback function is able to receive the response in proper JSON format in case of status code 201 but when I try to fetch the error response in errorCallback (status: 422) it shows something like this (logging the response in console):
Error: [object Response]
at status (grocery-market.js:447)
When I try to console.log the error response before wrapping it in new Error() like this,
checkStatus = (response) => {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
} else {
console.log(response.json()) //logging the response beforehand
return Promise.reject(new Error(response.statusText))
}
}
I get the below promise in console (the [[PromiseValue]] property actually contains the error text that i require)
Can someone please explain why this is happening only in error case, even though I'm calling response.json() in both error and success case?
How can I fix this in a clean manner?
EDIT:
I found that if I create another json() promise on the error response I am able to get the correct error :
fetch("api_url_here", {
method: 'some_method_here',
credentials: "same-origin",
headers: {
"Content-type": "application/json; charset=UTF-8",
'X-CSRF-Token': "some_token_here"
}
})
.then(checkStatus)
.then(function json(response) {
return response.json()
})
.then(function(resp){
successCallback(resp)
})
.catch(function(error){
error.json().then((error) => { //changed here
errorCallback(error)
});
});
But why do I have to call another .json() on the response in error case?
Short answer
Because your assumption that you parse the response content twice is incorrect. In the original snippet, the then after the then(checkStatus) is skipped when the error condition is met.
Long answer
In general, well-structured promise chain consists of a:
Promise to be fulfilled or rejected sometime in the future
then handler that is run only upon fulfillment of the Promise
catch handler that is run only upon rejection of the Promise
finally (optional) that is run when a Promise is settled (in either 2 or 3)
Each of the handlers in 2 and 3 return a Promise to enable chaining.
Next, fetch method of the Fetch API rejects only on network failures, so the first then method is called regardless of the status code of the response. Your first handler, the onFulfilled callback returns either a fulfilled or rejected Promise.
If fulfilled, it passes control to the next then method call in the chain, where you extract JSON by calling json method on the response, which is then passed as Promise value to the last then method to be used in the successCallback.
If rejected, the control is passed to the catch method call, which receives the Promise with the value set to new Error(response) that you then promptly pass to errorCallback. Therefore, the latter receives an instance of Error, whose value is an instance of Response from Fetch API.
That is exactly what you see logged: Error: [object Response], a result of calling toString method on an instance of Error. The first part is the constructor name, and the second is a string tag of the contents (of the form [type Constructor]).
What to do?
Since your API returns JSON response for every possible use case (201, 404, 422), pass the parsed response to both fulfilled and rejected promise. Also, note that you accidentally declared checkStatus on the global scope by omitting a var, const, or let keyword:
//mock Response object
const res = {
status: 200,
body: "mock",
async json() {
const {
body
} = this;
return body;
}
};
const checkStatus = async (response) => {
const parsed = await response.json();
const {
status
} = response;
if (status >= 200 && status < 300) {
return parsed;
}
return Promise.reject(new Error(parsed));
};
const test = () => {
return checkStatus(res)
.then(console.log)
.catch((err) => console.warn(err.message))
.finally(() => {
if (res.status === 200) {
res.status = 422;
return test();
}
});
};
test();
Additionally, since you already use ES6 features (judging by the presence of arrow functions), why not go all the way and use the syntactic sugar async/await provides:
(() => {
try {
const response = await fetch("api_url_here", {
method: 'some_method_here',
credentials: "same-origin",
headers: {
"Content-type": "application/json; charset=UTF-8",
'X-CSRF-Token': "some_token_here"
}
});
const parsed = await response.json(); //json method returns a Promise!
const {
status
} = response;
if (status === 201) {
return successCallback(parsed);
}
throw new Error(parsed);
} catch (error) {
return errorCallback(error);
}
})();
Note that when you are passing the parsed JSON content to the Error() constructor, an abstract ToString operation is called (see step 3a of the ECMA spec).
If the message is an object, unless the original object has a toString method, you will get a string tag, i.e. [object Object], resulting in the content of the object being inaccessible to the error handler:
(() => {
const obj = { msg : "bang bang" };
const err = new Error(obj);
//will log [object Object]
console.log(err.message);
obj.toString = function () { return this.msg; };
const errWithToString = new Error(obj);
//will log "bang bang"
console.log(errWithToString.message);
})();
Contrary, if you reject a Promise with the object passed as an argument, the rejected Promise's [[PromiseValue]] will be the object itself.
It's because in case of an error then handlers are skipped and the catch handler is executed, checkStatus returns a rejected promise and the remaining then chain is bypassed.
This is how I'd refactor your code.
checkStatus = async (response) => {
if (response.status >= 200 && response.status < 300)
return await response.json()
throw await response.json()
}
}
fetch("api_url_here", {
method: 'some_method_here',
credentials: "same-origin",
headers: {
"Content-type": "application/json; charset=UTF-8",
'X-CSRF-Token': "some_token_here"
}
})
.then(checkStatus)
.then(successCallback)
.catch(errorCallback);
P.S. this code can be made a bit better to look at visually by using async/await
Im trying to execute a function after the other one in Vue.js. I've already tried async/await, callback functions, .then, but it somehow doesnt want to load one after the other. What is a possible solution?
auth_mixin.js:
async auth () {
console.log("authban")
var token = this.getCookie("token")
var jsonData = {}
jsonData["token"] = token
console.log(jsonData)
var bodyFormData = new FormData();
bodyFormData.append('data', JSON.stringify(jsonData));
axios({
method: 'post',
url: 'backend/index.php?action=checkAuth',
data: bodyFormData,
headers: {'Content-Type': 'multipart/form-data'}
})
.then(function (response) {
console.log(response);
if(response.data.status==="OK"){
console.log("ok")
return true;
}else{
console.log("nem ok")
return false;
}
})
.catch(function (response) {
console.log(response);
return false;
});
}
Navbar.vue:
created () {
var result=false
this.auth().then(this.checkIfLoggedIn(result))
},
methods: {
checkIfLoggedIn (isLoggedIn) {
console.log("na ez lesz az erdekes "+isLoggedIn)
if(isLoggedIn === true){
console.log("true")
document.getElementById("logged_out").style.display="none";
document.getElementById("logged_in").style.display="block";
}else{
console.log("fail");
}
}
}
this.auth().then(this.checkIfLoggedIn(result))
You have two problems.
First: this.checkIfLoggedIn(result) calls checkIfLoggedIn immediately. You need to pass a function to then.
this.auth().then(() => this.checkIfLoggedIn(result))
Second: With that change, you call checkIfLoggedIn when auth resolves.
So when does auth resolve? Well, it is defined with the async keyword, so it resolves when it returns (unless it returns a promise, in which case it adopts that promise instead).
So what does it return? It has no return statement, so it returns undefined when it gets to the end … which is immediately after the call to axios (since you aren't awaiting that).
If you returned the return value of axios(...).etc then it wouldn't resolve until that promise resolved.
(Aside: You're using async, you should probably refactor to use await, try {} catch() {} instead of .then() and .catch()).
I have to timeout the function uploadData whenever the rest call fails due to some condition. I have tried with setInterval in a catch block but it didn't give me the required results. So how can I code to timeout my function in a failure condition within 5000ms? This is my code:
uploadData(filename,callback){
formData={
'filename'=fs.createReadStream(filename)
}
options{
methiod:POST,
url:url,
auth:this.auth,
headrers:this.headers,
formData:formData
}
rp(options).then((repos)=>{
var response={
'file':filename,
'status':'success',
'message':repos,
};
return callback(response);
}).catch(fn=setInterval((err)=>{
var response={
'file':filename,
'status':'failes',
'message':err.message,
}
return callback(response);
},5000));
}
A good way to accomplish such a feature is to use Promise.race with two promises: first is the one which makes a request, and the second is a timeout promise which resolves after a fixed time. Example:
const timeout = new Promise((resolve) => {
setTimeout(() => resolve({ timeout: true }), 5000);
});
const formData = {
'filename'=fs.createReadStream(filename)
}
const options = {
method: 'POST',
url,
auth: this.auth,
headrers: this.headers,
formData: formData
}
const request = rp(options);
// The first one to resolve will be passed to the `.then()` callback
Promise.race([request, timeout]).then((response) => {
if (response.timeout === true) {
return console.log('timeout');
}
console.log('api response', response);
});
So I've made a bit of reusable code here for node, and I'm applying it via async / await. Albeit I'm sure I am misunderstanding a lot here when working with this... But, I swear, I have one project I'm using this code that it works, and another where it doesn't.
Im using request and request-promise.
UrlRequest: function( opts ) {
return new Promise( (resolve, reject) => {
request( opts,
function(error, request, body) {
if (error)
reject( {error: true, msg: error} );
else
resolve( {body, request} );
});
})
.catch(err => reject( {error: true, msg: err} ));
}
I am fairly sure the .catch() is wrong. But it didn't error out in my 1st project. So i'm trying to figure out the proper way of doing this. The few articles I've looked through is where I came up with this function for usage. I also know if any error actually happens ( this case included ), it will throw a UnhandledPromiseRejectionWarning error. So how is this properly handled?
How I use it:
(async () => {
var result = await Promise.UrlRequest( {
url: "...",
method: "GET",
headers: DefaultHeaders
} );
// do stuff with result...
}) ();
Since you already installed request-promise, you don't need constructing the Promise as you are doing. Simply use the it instead of request then you would have a promise returned. Something similar to this should work:
const request = require('request-promise')
request(opts)
.then((res) => {
// Process res...
})
.catch((err) => {
// Handle error...
});
You can proceed to wrap it in your UrlRequest function and use with async as follows:
UrlRequest: async ( opts ) => {
try {
const response = await request(opts);
return response;
} catch (error) {
// Handle error
}
}
In the case that you want to use then() and catch(), you can do this:
UrlRequest: ( opts ) => {
return request(opts)
.then(response => response)
.catch (error) {
// Handle error
}
}
With request-promise, you don't need to write your own Promise wrapper
// make sure you're using the promise version
const request = require('request-promise')
var opts = {
...
resolveWithFullResponse: true // <--- <--- to get full response, response.body contains the body
};
// if you dont plan to use UrlRequest as constructor, better name is starting with lowercase: urlRequest, some naming convention
UrlRequest: async function( opts ) {
let res;
try {
res = await request(opts);
} catch (e) {
// handle error
throw e
}
return res;
}
Note: async function wraps the return in Promise