I am learning how to use Promise without libraries. From what I have read, I could chain Promise together and then add .catch in the end for error handling.
What do I expect
So if I change the URL to some false url, shouldn't I be catching the error and stop the entire program to be continuing?
What Am I seeing now?
When I put a false url, the program just throws out an error, instead of handling it like an rejection.
const request = require("request");
new Promise((resolve, reject) => {
request(
"http://maps.googleapis.com/maps/api/geocode/json?address=321%20i%20st%20davis",
(err, res, body) => {
if (err) {
reject("bad call on geo code!");
}
resolve(JSON.parse(body).results[0].geometry.location);
}
);
})
.then(res => {
const {lat, lng} = res;
return new Promise((resolve, reject) => {
request(
`https://api.darksky.net/forecast/6fb416a8313aabd902a22558e07cc032/${lat},${lng}`,
(err, res, body) => {
if (err) {
reject("bad call on darksky");
}
resolve(JSON.parse(body));
}
);
});
})
.then(res => {
const currentTemp = res.currently.temperature;
const feelTemp = res.currently.apparentTemperature;
const temps = {currentTemp, feelTemp};
return new Promise((resolve, reject) => {
request(
"http://ron-swanson-quotes.herokuapp.com/v2/quotes",
(err, res, body) => {
if (err) {
reject("bad call on quotes");
}
resolve({temps, body});
}
);
});
})
.then(res => {
console.log(
`Today's weather is ${res.temps.currentTemp}, and it feels like ${res
.temps
.feelTemp}! \nAnd here is your stupid quote of the day: \n${JSON.parse(
res.body
)[0]}`
);
})
.catch(err => {
console.log(err);
});
Error Message:
This isn't really meaningful, basically the error did not stop the program, which just passed down to the next promise. That promise receive the error but could not parse it because it is not in expected JSON format.
SyntaxError: Unexpected token < in JSON at position 0
at JSON.parse (<anonymous>)
at Promise.then.then.then.res (/Users/leoqiu/reacto/playground/6_promiseMethod.js:48:74)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
When you call reject() inside your if statement, you don't return and you don't use an else so your resolve(JSON.parse(body).results[0].geometry.location); still gets executed and that throws an exception.
You can change to this:
new Promise((resolve, reject) => {
request(
"http://maps.googleapis.com/maps/api/geocode/json?address=321%20i%20st%20davis",
(err, res, body) => {
if (err) {
reject("bad call on geo code!");
return;
}
resolve(JSON.parse(body).results[0].geometry.location);
}
);
})
It is a common mistake that people think reject() works like break or some other control flow statement because reject() is a type of promise control flow. But, it doesn't stop execution in your block so you need to either return after it or use an else.
Or, I prefer to use if/else because I think it makes the logic even more obvious:
new Promise((resolve, reject) => {
request(
"http://maps.googleapis.com/maps/api/geocode/json?address=321%20i%20st%20davis",
(err, res, body) => {
if (err) {
reject("bad call on geo code!");
} else {
resolve(JSON.parse(body).results[0].geometry.location);
}
}
);
})
Based on Patrick Evans suggestion...
reject does not stops the program from running, so the error message gets pass down to the next Promise, which is why is throwing a json parsing error.
The solution is simply put a return in the rejection.
if (err) {
reject("bad call on geo code!");
return err;
}
Related
I've already searched among a lot of questions asked by other users but I can't still figure out why my function doesn't work properly.
I wrote this code:
async function getFile(c, sourcePath, fileName, destPath) {
return await new Promise((resolve, reject) => {
c.cwd(sourcePath, function(err, currentDir) { // "c" is an instance of ftp module's class, "cwd" is one of its methods to change working directory
err = new Error('trying to throw an error for testing the func');
if (err) reject(err);
c.get(fileName, function(err, stream) {
if (err) reject(err);
stream.once('close', function() { resolve(); });
stream.pipe(fs.createWriteStream(destPath));
});
});
}));
}
(async function() {
try {
await getFile(c, '/path/to', 'file.xls', 'file.xls');
} catch (err) {
throw err;
}
})();
Node returns an UnhandledPromiseRejectionWarning, and I don't understand why. I have returned a promise in the first function and then called that function in another one correctly structured with async-await syntax. What is wrong with my code?
I have an internal API that I would like to post data. Depends on some cases, I am seeing errors. So what I would like to do is to call it again if there is an error occurred.
What I did was to create a counter to pass it to the function and call the function recursively as below. This gives me the error as below:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Here is how I call the function:
....
private RETRY_API = 1;
....
try {
await this.callAPI(request, this.RETRY_API);
} catch (error) {
console.log('error', error);
}
This program never comes to the catch block above.
And here is my actual function that I call the API:
private async callAPI(request, retry) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request, async(err: any, httpCode: number, data) => {
if (this.RETRY_API == 2) {
return reject(err);
} else if (err) {
this.callAPI(request, retry);
this.RETRY_API++;
} else if ( httpCode !== 200 ) {
this.RETRY_API = 2;
// some stuff
} else {
this.RETRY_API = 2;
// some stuff
return resolve(data);
}
});
})
}
Not sure what I am missing. If there is a better way to call the API twice if an error occurred, that would be great if you let me know.
Let's organize a little differently. First, a promise-wrapper for the api...
private async callAPI(request) {
return new Promise((resolve, reject) => {
someService.postApiRequest('api/url', request,(err: any, httpCode: number, data) => {
err ? reject(err) : resolve(data);
});
});
}
A utility function to use setTimeout with a promise...
async function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
Now, a function that calls and retries with delay...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
return await callAPI(request);
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
If you can't force a failure on the api to test the error path some other way, you can at least try this...
private async callAPIWithRetry(request, retryCount=2, retryDelay=2000) {
try {
// I hate to do this, but the only way I can test the error path is to change the code here to throw an error
// return await callAPI(request);
await delay(500);
throw("mock error");
} catch (error) {
if (retryCount <= 0) throw err;
await delay(retryDelay);
return callAPIWithRetry(request, retryCount-1, retryDelay);
}
}
It looks like you need to add return await to the beginning of the line this.callAPI(request, retry); in callAPI function.
Similarly there are some condition blocks that doesn't resolve or reject the promise. While it might work okay, it's considered bad practice. You want to either resolve or reject a promise.
I've accomplished calling an API a second time when I received an error by using axios' interceptors functions.
Here is a code snippet you can review:
axios.interceptors.response.use(
// function called on a successful response 2xx
function (response) {
return response;
},
// function called on an error response ( not 2xx )
async function (error) {
const request = error.config as AxiosRequestConfig;
// request is original API call
// change something about the call and try again
// request.headers['Authorization'] = `Bearer DIFFERENT_TOKEN`;
// return axios(request)
// or Call a different API
// const new_data = await axios.get(...).then(...)
// return new_data
// all else fails return the original error
return Promise.reject(error)
}
);
Try replacing
if (this.RETRY_API == 2)
with
if (this.RETRY_API > 1)
Try to handle every exception in my async code (nodeJS, ExpressJS):
Here is almost pseudo code. I use limiter (npm limiter) module with method removeTokens (num, callback(err,remainingRequest)). Big part of code is inside the callback, and I wanna catch and throw any error there to the handler, but for now the error inside callback is still marked as "unhandled exception" and I don't understand why.
app.post('/', async (req, res) => {
try {
...
return getAll();
async function getAll () {
limiter.removeTokens(1, async (err, remainingRequest) => {
try {
throw new Error('THROWN')
} catch (error) {
throw error
}
})
}
} catch (error) {
console.log(error);
}
});
You shouldn't pass async functions into things that don't expect them (unless you catch all errors, as you are with your app.post callback). Instead, give yourself a wrapper for limiter.removeTokens that returns a promise:
function removeTokens(limiter, id) {
return new Promise((resolve, reject) => {
limiter.removeTokens(id, (err, remainingRequest) => {
if (err) {
reject(err);
} else {
resolve(remainingRequest);
}
});
});
}
(You might also look into util.promisify for that.)
Then:
app.post('/', async (req, res) => {
try {
...
await getAll(); // *** Or you might just use `removeTokens(limiter, 1)` directly here
function getAll() {
return removeTokens(limiter, 1);
}
} catch (error) {
console.log(error);
}
});
Here it is using removeTokens directly:
app.post('/', async (req, res) => {
try {
...
await removeTokens(limiter, 1);
} catch (error) {
console.log(error);
}
});
Firstly if possible please share as much code as you can as then it is easy for us to debug where the problem might be.
Coming you your question i think the problem is that in your try..catch block you are throwing the error instead of handling it with a reject. Below i have pasted a code block which you can try and let me know if it works for you. Please not the syntax might be different but the idea is that you have to reject the Promise in case of error.
`````````limiter.removeTokens(1, async (err, remainingRequest) => {
````````````try {
```````````````throw new Error('THROWN')
````````````} catch (error) {
```````````````return Promise.reject(error) //
````````````}
`````````})
``````}
```} catch (error) {
``````console.log(error);
```}
})
I have code like this:
Promise.all(venue.map(venue => {
return Promise.all(concat_all.map(tgl => {
pool.query("INSERT INTO peminjaman_venue VALUES (?,?,?,?,?,?,?,?,?,?,?)",
[id_event, venue, nama_lengkap_peminjam, jabatan_nim_peminjam, jumlah_personel,
id_google_calendar, waktu_mulai_rutin, waktu_selesai_rutin, tgl,
tgl, fasilitas_lain],
function (err, rows, fields) {
if (err) throw err;
})
}))
}).then(
req.flash('message_success', 'Berhasil mengajukan event'),
res.redirect('/pengajuan_event'))
.catch(
req.flash('message_err', 'Gagal mengajukan event'),
res.redirect('/pengajuan_event')
))
The code returns error Can't set header after they are sent, that indicates the res.redirect() is called multiple times. But the code works. The data inserted to the db successfully. I changed the code below and the code just doesnt work at all.
Promise.all(venue.map(venue => {
return Promise.all(concat_all.map(tgl => {
pool.query("INSERT INTO peminjaman_venue VALUES (?,?,?,?,?,?,?,?,?,?,?)",
[id_event, venue, nama_lengkap_peminjam, jabatan_nim_peminjam, jumlah_personel,
id_google_calendar, waktu_mulai_rutin, waktu_selesai_rutin, tgl,
tgl, fasilitas_lain],
function (err, rows, fields) {
if (err) throw err;
})
}))
}).then(() = >{
req.flash('message_success', 'Berhasil mengajukan event')
res.redirect('/pengajuan_event'))
}
.catch((err) => {
req.flash('message_err', 'Gagal mengajukan event')
res.redirect('/pengajuan_event')
}
)
)
I would create an array to hold all your async requests. So
const promises = [];
promises.push(async request 1);
promises.push(async request 2);
...
Promise.all(promises).then(result => {
// do something ...
})
I would also refactor the code a bit to use async/await to remove some of those brackets. It is hard to read with all those nested promises.
I'm writing a module that uses the Google API, but am wrapping everything that is callback based in a promise. This is the code of the problem area
file1.js
var File2 = require('file2')
var api = new File2()
api.auth().then(auth => {
api.search('example').then(res => {
...do some stuff...
})
}).catch(err => {
console.log('1') //Not being run
throw err
})
file2.js
class File2(){
auth() {
...works fine and resolves...
}
search() {
return new Promise((resolve, reject) => {
googleapi.somemethod(options, (err, res) => {
if(err) {
console.log('2') // DOES run
reject(new Error(err))
}
resolve(res.field) //Program crashes here because reject didn't actually reject
})
})
}
The call to auth is working just fine, but the call to search (and more specifically googleapi.somemethod) is failing, and err is defined. I check for err, and console.log('2') runs, but then console.log('1') in catch doesn't run, the error isn't thrown, and the program crashed on resolve(res) because res is undefined. I've tried putting the error catcher as the second argument to then instead of using catch, but that still doesn't work
api.search('example').then(res => {
...do some stuff...
}, err => {
console.log('2') // Still doesn't run
throw err
})
I'm running Node v6.2.1
You should return the promise:
var File2 = require('file2')
var api = new File2()
api.auth().then(auth => {
return api.search('example').then(res => { // return the promise
return ...
})
}).catch(err => {
console.log('1') // Not being run
throw err
})
Also, if you don't need auth inside search then you can unnest those promises:
var File2 = require('file2')
var api = new File2()
api.auth().then(auth => {
return api.search('example')
}).then(res => {
return ...
}).catch(err => {
console.log('1') //Not being run
throw err
})
calling reject() does not stop your program, all codes below will be executed too.
Please update from
if(err) {
console.log('2') // DOES run
reject(new Error(err))
}
resolve(res.field) //Program crashes here because reject didn't actually reject
to
if(err) {
console.log('2') // DOES run
reject(new Error(err))
}
else {
resolve(res.field) //Program crashes here because reject didn't actually reject
}
* update *
or you can shorten your code to
if(err) {
console.log('2') // DOES run
return reject(err) // no need to new Error object
}
resolve(res.field) //Program crashes here because reject didn't actually reject