Proper mindset for use of promises? - javascript

I've only recently looked at promises (JS not being my forte) and I'm not sure what the proper way to do this is. Promises are supposed to prevent right-drifting code but when I end up with somewhat complex logic I end up nested far too deep anyway, so I'm convinced I'm doing it wrong.
If I'm returning both successes and failures as json values, and I want to handle malformed json as well, I immediately think to do something like this:
fetch('json').then(function (result) {
return result.json();
}).catch(function (result) {
console.error("Json parse failed!");
console.error(result.text);
}).then(function (wat) {
// if (!result.ok) { throw...
}).catch(function (wat) {
// Catch http error codes and log the json.errormessage
});
Of course, this won't work. This is stereotypical synchronous code. But it's the first thing that comes to mind. Problems I can see:
How do I get both the response and the json output?
How do I get separate control flow for errors and successes?
How do I catch a json parse error on both types of response?
My best attempt involves nesting to the point where I might as well be using callbacks, and it doesn't work in the end because I still haven't solved any of the above problems:
fetch('json').then(function (response) {
if (!response.ok) {
throw response;
}
}).then(
function (response) {
response.json().then(function (data) {
console.log(data);
});
},
function (response) {
response.json().then(function (data) {
console.error(data.errormessage);
});
}
).catch(function () {
console.error("Json parse failed!");
// Where's my response????
});
What's the "Right" way to do this? (Or at least less wrong)

If you want to call response.json() anyway (for successful and failed response) and want to use the response together will the response data. Use Promise.all:
fetch('json')
.then(response => Promise.all([response, response.json()]))
.then(([response, data]) => {
if (!response.ok) {
console.error(data.errormessage);
} else {
console.log(data);
}
})
.catch(err => {
if (/* if http error */) {
console.error('Http error');
} else if (/* if json parse error */)
console.error('Json parse failed');
} else {
console.error('Unknown error: ' + err);
}
});

You shouldn't use exceptions for control flow in Promises any more than you should when not using Promises. That's why fetch itself doesn't just reject the promise for status codes other than 200.
Here's one suggestion, but the answer will necessarily depend on your specific needs.
fetch('json').then(function (response) {
if (!response.ok) {
response.json().then(function (data) {
console.error(data.errorMessage);
});
return ...;
}
return response.json().catch(function () {
console.error("Json parse failed!");
return ...;
});
}).catch(function (e) {
console.error(e);
return ...;
});

Related

My Promise going into both then and catch

I have a set of nested promises shown below. The expected behaviour here is for 1 to be printed, then 2 to be printed and then for the callback to be called under dropbox_functions.moveFolder('test', data.ui). However what is happening is that 1 is printed then 2 is printed and the 2.1 is printed, so the 2 promise is going into the then and the catch. I cant work out why.
dropbox_functions.createBatchFolder(data.ui)
.then(function(response) {
console.log('1')
console.log(response)
dropbox_functions.checkScannerFolderExists('test')
.then(function(response) {
console.log('2')
console.log(response)
dropbox_functions.moveFolder('test', data.ui)
.then(function(response) {
console.log(response)
callback(null, data)
})
.catch(function(error) {
console.log(error);
callback('Data not copied from scanner', data)
});
})
.catch(function(error) {
console.log('2.1')
console.log(response)
dropbox_functions.createDataFolder(data.ui)
.then(function(response) {
console.log(response)
callback('No scanned folder', data)
})
.catch(function(error) {
console.log(error);
callback('Data Folder not created', data)
});
});
// callback(null, data)
})
.catch(function(error) {
console.log('1.2')
console.log(error)
callback('Folder not created', data)
});
There must be an error thrown, after execution of line console.log('2'). If an error thrown within a promise execution, the next immediate catch block will catch the particular error.
console.log('2')
console.log(response) <-- Here
dropbox_functions.moveFolder('test', data.ui) <--- Here
.then(function(response) {
console.log(response)
callback(null, data)
})
.catch(function(error) { <-- or here
console.log(error);
callback('Data not copied from scanner', data)
});
This isn't an answer but an recommendation. The whole point of promises is that you don't have to nest them, but can chain them instead. This is done by simply returning a new promise as return value of the function provided to then. You can then chain another then statement after it that handles the newly resolved promise.
dropbox_functions
.createBatchFolder(data.ui)
.then(function (response) { // response of createBatchFolder
console.log('1');
console.log(response);
return dropbox_functions.checkScannerFolderExists('test');
})
.then(function (response) { // response of checkScannerFolderExists
console.log('2');
console.log(response);
return dropbox_functions.moveFolder('test', data.ui);
})
.then(function (response) { // response of moveFolder
console.log(response);
callback(null, data);
})
.catch(function (error) { // some error occurred
console.error(error);
callback(error, data);
});
If you need to do something for specific errors you can check the name and/or description properties of the error or convert it to a string by calling toString() on it.
Furthermore by calling a callback function yourself you still introduce callback hell into your program. I'd recommend returning a promise instead and work with that. If the code above is the last statement of your function you can simply return a resolved promise from the last then statement.
function yourFunction() {
// ...
return dropbox_functions
.createBatchFolder(data.ui)
// ...
.then(function (response) { // response of moveFolder
console.log(response);
return Promise.resolve([null, data]);
})
.catch(function (error) { // some error occurred
console.error(error);
return Promise.reject([error, data]);
});
}
If it's not the last statement simply save the resulting promise into a variable and return that after you did you other stuff. You could even leave out the catch statement if all you do is forward it into Promise.reject(...) (this is currently not the case since you add data too). This can then be handled by the code calling yourFunction.

Axios HTTP call is always treated as succeed

So I'm trying for multiple ways to get error response status from my axios HTTP call and something weird is happening.
getData() {
axios.get(`/api/article/getObserved.php`, axiosConfig)
.then(response => {
console.log('success');
console.log(response);
})
.catch(err => {
console.log('error');
console.log(err.status);
console.log(err.response.status)
});
}
So I'm calling my getObserved endpoint and although it's returning http_response_code(503); it's going to .then() part because it console log 'success' string.
this is output from console:
GET http://localhost/obiezaca/v2/api/article/getObserved.php 503 (Service Unavailable)
success favouriteArticles.vue?31bd:83
I've done hundreds of calls like this and this .catch was always catching error even tho I'm not throwing exception like in other lenguages I would do. However I also tried like this:
getData() {
axios.get(`/api/article/getObserved.php`, axiosConfig)
.then(response => {
console.log('success');
console.log(response);
}, function (err) {
console.log('error');
console.log(err.status);
console.log(err.response.status);
})
.catch(err => {
console.log('error');
console.log(err.status);
console.log(err.response.status)
});
}
But it still doesn't console 'error' although I have this 503 bad request returned from my endpoint. Why?
I also would like to add that I dont think my endpoint is not working correctly because I was testing it with tests and manually by cURL and POSTMAN and everything was fine.
Edit since response is undefined when I don't get data from my endpoint and I need to handle only one error (there is data or not) I have just do something like this:
getData() {
axios.get(`/api/article/getObserved.php`, axiosConfig)
.then(response => {
if(response) {
this.articles = response.data.records;
} else {
this.noFavourite = true;
this.articles = [];
}
});
and it's working. I'll pray to not get into same issue with some call where I'll need to handle several different errors.
This issue was related to my httpInterceptor
import axios from 'axios';
import { store } from '../store/store';
export default function execute() {
axios.interceptors.request.use(function(config) {
const token = store.state.token;
if(token) {
config.headers.Authorization = `Bearer ${token}`;
//console.log(config);
return config;
} else {
return config;
}
}, function(err) {
return Promise.reject(err);
});
axios.interceptors.response.use((response) => {
return response;
}, (err) => {
console.log(err.response.status)
return Promise.reject(err); // i didn't have this line before
});
}
which wasn't returning promise on error response so after in promise of http call it somehow treated it as success. After adding return Promise.reject(err); inside my interceptor it's working fine

Error : Callback was already called when using pg-promise with async series

I'm having trouble understanding the output printed why executing this code :
1
2
Unhandled rejection Error: Callback was already called.
It seems like both then and catch are executed when the query is successful.
Any idea ?
Cheers
async.series([
function(callback) {
db.none(query)
.then(function () {
return callback(null, true);
})
.catch(function (err) {
return callback(err, null);
});
},
function(callback) {
db.any(query)
.then(function (data) {
console.log('1')
return callback(null, data);
})
.catch(function (err) {
console.log('2')
console.log(err);
return callback(err, null);
});
}
],
function(err, results) {
if (results && !results[1].isEmpty()) {
// do something
}
});
EDIT :
TypeError: results[1].isEmpty is not a function
It seems like the problem come from the rest of the code and was just a simple undefined function error, thanks.
But i still don't understand something : why is this error catched inside the second query instead of outside the async queries ?
I'm the author of pg-promise.
You should never use async library with pg-promise, it goes against the concept of shared/reusable connections.
Implementation with proper use of the same connection, via a task:
db.task(t => {
return t.batch([
t.none(query1),
t.any(query2)
]);
})
.then(data => {
// data[0] = null - result of the first query
// data[1] = [rows...] - result of the second query
callback(null, data); // this will work, but ill-advised
})
.catch(error => {
callback(error, null); // this will work, but ill-advised
});
See also: Chaining Queries.
However, in your case it looks like when you call the successful callback(null, data), it throws an error, which in turn results in it being caught in the following .catch section. To test this, you can change your promise handler like this:
.then(data => {
callback(null, data);
}, error => {
callback(error, null);
});
It should normally throw an error about Promise missing .catch because you threw an error while in .then and there is no corresponding .catch chained below, which you can also check through this code:
.then(data => {
callback(null, data);
}, error => {
callback(error, null);
})
.catch(error => {
// if we are here, it means our callback(null, data) threw an error
});
P.S. You really should learn to use promises properly, and avoid any callbacks altogether. I only provided an example consistent with your own, but in general, converting promises into callbacks is a very bad coding technique.
This is what happens:
callback(null, data) is called within the context of the .then();
async notices that this was the last item of the series, so it calls the final handler (still within the context of the .then());
the final handler throws an error;
because the code runs in the context of .then(), the promise implementation catches the error and calls the .catch();
this calls the callback again;
PoC:
const async = require('async');
async.series([
callback => {
Promise.resolve().then(() => {
callback(null);
}).catch(e => {
callback(e);
});
}
], err => {
throw Error();
})
Have you try to define your function externally:
function onYourFunction() {
console.log('Hi function');
}
and than do:
.then(onYourFunction) //-->(onYourFunction without parentheses )
Unfortunately i don't use pg-promise but i can advise promise
at this point i create all promises that are necessary:
function createPromise(currObj) {
return new Promise(function (resolve, reject) {
currObj.save(function (errSaving, savedObj) {
if(errSaving){
console.log("reject!");
return reject(errSaving, response);
}
console.log('currObj:' + currObj);
return resolve(savedObj);
});
});
}
and then in cascades:
var allPromiseOs = Promise.all(promise1, promise2, promise3);

Javascript http get success function capturing error response

I am making a http request to a url which is returing a 500 error response(This is the expected behavior). But this is error is getting captured in the success function instead of error function.
$http.get("myUrl")
.then(function (response) {
console.log(response);
}
.function (error) {
// Handle error here
});
Please help in understanding this and the correct way to use this.
It should be either:
$http.get("myUrl")
.then(function (response) {
console.log(response);
}
,function (error) {
// Handle error here
});
Or
$http.get("myUrl")
.then(function (response) {
console.log(response);
})
.catch (function(error) {
// Handle error here
});
If this is angulars $http, it's supposed to be something like this:
$http.get("myUrl")
.then(
function (response) {
console.log(response);
},
function (error) {
// Handle error here
}
);
You want two functions as your arguments to then(). The first is your successCallback, the second your errorCallback.
As an alternative you may add an catch() to your promise chain. Which is easier to read and prevents errors like yours.
I had an interceptor in my application which was causing the problem.
All my error responses were intercepted and returned without a status. Below is the code.
return {
'responseError': function(config) {
if(config.status===401){
//Do Something
return config;
}
return config;
}
}
Changing the return statement to return $q.reject(config); started returning the correct status.

How to proper chain promises calls that depend on one another?

I have the following code:
const request = require('request-promise');
request(validateEmailOptions).then(function(result) {
if (result.valid) {
request(createUserOptions).then(function (response) {
if (response.updatePassword) {
request(modifyUserOptions).then(function (response) {
return res.redirect('/signin');
}).catch(function(error) {
return res.redirect('/error');
});
}
}).catch(function(error) {
return res.redirect('/error');
});
} else {
return res.redirect('/error');
}
})
.catch(function (reason) {
return res.redirect('/error');
});
Basically, it's a chain of request call, each one based on the result of the previous call. The problem is that I have many more lines in each condition and as a result, my code is bloated and hard to read and follow. I want to know if there is a better way to write the call chain using request-promises or simply request and bluebird.
You can unnest the promises. Think that this:
f(a).then(function(a) {
return g(b).then(function(b) {
return h(c)
})
})
Is the same as:
f(a).then(function(a) {
return g(b)
}).then(function(b) {
return h(c)
})
I would recommend failing as early as possible, that means handling the error condition first, and having meaningful error messages to be able to log them if need be. Finally, you can propagate the error and handle it in a single catch. To put it in context in your code:
request(validateEmailOptions).then(function(result) {
if (!result.valid) {
throw new Error('Result is not valid');
}
return request(createUserOptions);
}).then(function(response) {
if (!response.updatePassword) {
throw new Error('Password is not updated');
}
return request(modifyUserOptions);
}).then(function(response) {
return res.redirect('/signin');
}).catch(function(error) {
// you may want to log the error here
return res.redirect('/error');
});

Categories