I've been trying to get the rejects of my asynchronous functions to bubble back up to their callers, but it's not working for some reason. Here's some tested example code:
"use strict";
class Test {
constructor() {
this.do1();
}
async do1() {
try { this.do2(); } catch(reason) { console.error(reason); }
}
async do2() {
for(let i = 0; i < 10; i++) {
await this.do3();
console.log(`completed ${i}`);
}
console.log("finished do1");
}
async do3() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(Math.random() < 0.3) reject('###rejected');
else resolve("###success");
}, 1000);
});
}
}
export default Test;
Chrome just gives me this every time: Unhandled promise rejection ###rejected.
Any idea why this is happening? I'd like to be able to handle all thrown errors from a higher level than do2() (the above example works fine if the try/catch is in do2() and wraps await this.do3();). Thanks!
Edit: To be a bit more explicit, if I take the try/catch out of do1() and put it in do2() as follows, everything works fine:
async do2() {
try {
for(let i = 0; i < 10; i++) {
await this.do3();
console.log(`completed ${i}`);
}
console.log("finished do1");
} catch(reason) { console.error(reason); }
}
async do1() {
try {
await this.do2();
}
catch(reason) {
console.error(reason);
}
}
do2 is an asynchronous function. And you call it without await. So, when it completes there's no try-catch clauses around it.
See this question and this article for more details.
Related
I am trying to build a wrapper over Notion JS SDK's iteratePaginatedAPI that handles errors as well. I feel particularly lost on how do I catch API errors in such a way that I can actually retry them (aka retry the iteration that failed). Here's my attempt:
async function* queryNotion(listFn, firstPageArgs) {
try {
for await (const result of iteratePaginatedAPI(listFn, firstPageArgs)) {
yield* result
}
} catch (error) {
if (error.code === APIErrorCode.RateLimited) {
console.log('rate_limited');
console.log(error);
sleep(1);
// How would I retry the last iteration?
}
}
}
Coming from the Ruby world, there is a retry in a rescue block. Any help would be appreciated!
Very interesting problem. The issue is that the exception comes from the for await itself, not from its body, so you cannot catch it there. When the exception hits, loops are over.
Note that the iterator might be done after a rejection/exception, in which case there is nothing you can do except starting a new one.
That said, you can always call Iterator.next() yourself and process the result manually. The next() call of an async iterator will return an object like {value: Promise<any>, done: boolean}, and when running it in a loop, you can await the promise in a try..catch and only exit the loop when done becomes true:
async function* queryNotion(listFn, firstPageArgs) {
const asyncGenerator = mockIteratePaginatedAPI(listFn, firstPageArgs)
while (true) {
const current = asyncGenerator.next()
if (current.done) {
break
}
try {
yield* await current.value
} catch (e) {
console.log(`got exception: "${e}" - trying again`)
continue
}
}
}
function* mockIteratePaginatedAPI(a, b) {
for (let i = 0; i < 8; i++) {
yield new Promise((resolve, reject) => setTimeout(() => [3, 5].includes(i) ? reject(`le error at ${i}`) : resolve([i]), 500))
}
}
(async function() {
for await (const n of queryNotion('foo', 'bar')) {
console.log(n)
}
})()
If we keep a reference to the generator, we can also put it back into a for async. This might be easier to read, however a for await ...of will call the iterator's return() when exiting the loop early, likely finishing it, in which case, this will not work:
async function* queryNotion(listFn, firstPageArgs) {
const asyncGenerator = mockIteratePaginatedAPI(listFn, firstPageArgs)
while (true) {
try {
for await (const result of asyncGenerator) {
yield* result
}
break
} catch (e) {
console.log('got exception:', e, 'trying again')
}
}
}
function* mockIteratePaginatedAPI(a, b) {
for (let i = 0; i < 8; i++) {
yield new Promise((resolve, reject) => setTimeout(() => [3, 5].includes(i) ? reject(`le error at ${i}`) : resolve([i]), 500))
}
}
(async function () {
for await (const n of queryNotion('foo', 'bar')) {
console.log(n)
}
})()
Simply add a continue statement inside your if
async function* queryNotion(listFn, firstPageArgs) {
try {
for await (const result of iteratePaginatedAPI(listFn, firstPageArgs)) {
yield* result
}
} catch (error) {
if (error.code === APIErrorCode.RateLimited) {
console.log('rate_limited');
console.log(error);
await sleep(1);
continue; // retry the last iteration
}
}
}
I am dealing with the following scenario in javascript. tt resembles a function from a package that return reject promise if something didn't happen. I want to Booleanize the await tt() in the test() function if that reject promise is triggered. the current setup results in catching the error and the else() block is not executed. is there a way to overcome that?
async function tt(){
return Promise.reject('failed');
}
async function test(){
if (somecondition && await tt()) //assume somecondition is true
{
console.log("accept")
}
else
{
console.log("reject")
}
}
test()
.catch(err=>console.log(err))
I want to avoid using .then(res ... ).catch(err ...)
You can tt in your own function that catches the error:
async function wrapped_tt() {
try {
await tt();
return true; // or return await tt(); depending on what tt returns
} catch {
return false;
}
}
later
if (somecondition && await wrapped_tt()) {
Of course you may want to check the error thrown by tt and only decide to return false for some of those errors.
So you don't care about the resolved value or the rejection error? Sure:
async function tt() {
return Promise.reject("failed");
}
function promiseAsBoolean(p) {
return p.then((s) => true, (e) => false);
}
async function test() {
if (somecondition && (await promiseAsBoolean(tt()))) {
console.log("accept");
} else {
console.log("reject");
}
}
I'm trying to get data from Hubspot and write the JSON it returns to a file. I'm specifically using this package https://www.npmjs.com/package/hubspot because it has a built-in failsafe for Hubspot's rate limits.
Unfortunately, I haven't been coding as much lately, and I ran up against an async issue with my code. It would be awesome if someone could tell me what I'm doing wrong here because I really need to get this script working.
My code:
const Hubspot = require('hubspot');
const fs = require('fs');
const hubspot = new Hubspot({ apiKey: 'apiKey' });
let engage = [];
const vid = [
'dummyId', 'dummyId2', 'dummyId3'
];
function createFile () {
fs.writeFile('./Engagements.json', engage, (err) => {
if (err) {
console.log(err);
return;
}
console.log('Success!');
});
}
(function () {
for (i = 0; i <= vid.length; i++) {
hubspot.engagements.getAssociated(hubspot.contacts, vid.i)
.then(results => {engage.push(results)});
}
setTimeout(createFile, 10000);
})();
And here's the error message I'm getting:
(node:37315) 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: 53)
Here is a correct .catch implementation. You should still implement #KevinB suggestion he made in the comments after you find and fix the error.
(function() {
for (i = 0; i <= vid.length; i++) {
hubspot.engagements.getAssociated(hubspot.contacts, vid[i])
.then(results => {
engage.push(results);
})
.catch(err => console.log(err));
}
setTimeout(createFile, 10000);
})();
It's a little difficult to debug from the snippet but from the error message you can try this:
(function() {
for (i = 0; i <= vid.length; i++) {
hubspot.engagements.getAssociated(hubspot.contacts, vid.i).then(results => {
engage.push(results)
}).catch(err => console.log(err))
}
setTimeout(createFile, 10000);
})();
Basically, you need a catch so that we do not get an unhandled promise rejection... This might not solve the whole thing but it's a step in the right direction...
Shouldn't you be waiting until all the promises return? Instead of depending upon the setTimeout, you might be better waiting for all the promises (look for Promise.all). You can also have a look at the article (https://davidwalsh.name/promises-results)
Promise.all(promises.map(p => p.catch(() => undefined)));
In your case it will something like
(function () {
var promises = [];
for (i = 0; i <= vid.length; i++) {
promises.push(hubspot.engagements.getAssociated(hubspot.contacts, vid.i));
}
Promise.all(promises.map(p => p.catch(e => e))).then(results => {
results.forEach(result => {
if (!(result instanceof Error)) {
engage.push(result);
}
});
createFile();
});
})();
I am deleting somefiles using fs.unlink and then I want to run some code. Due to the async nature of JS what is happening is that my code after unlinking is called before the callback of unlink. How can i Syncronise this? Is promises the only way ?
fs.unlink("FileName",function(err){
console.log("RUN");
})
for(let i = 0; i<10;i++) {
console.log(i);
}
RESULT :
1
2
3
4
5
6
7
8
9
RUN
The problem with using promises is that : If i have many files to delete, then i will have maintain a count of the promises and then check how many have been resolved. This i want to avoid
In this situation, you can use fs.unlinkSync, the synchronous version of fs.unlink:
try {
fs.unlinkSync("FileName");
console.log('Removing file successful!');
} catch(e) {
// TODO: handle errors here
};
console.log("RUN");
for(let i = 0; i<10;i++) {
console.log(i);
}
As #Keith rightfully mentions in the comments: synchronous operations like this should be use sparingly. If you have large numbers of files to delete, it may be better to use the asynchronous fs.unlink() because you can "start" more of those concurrently (tradeoff: start too many and the performance may suffer because of I/O saturation).
Using Promises is a good solution, you don't have to track the promises yourself, bluebird will do it for you:
const Promise = require('bluebird');
function unlinkFile(fileName) {
return new Promise((resolve, reject) => {
fs.unlink(fileName, function (err) {
if (err) {
return reject(err);
}
resolve();
});
});
}
Promise.all([unlinkFile('01.txt'), unlinkFile('02.txt'), unlinkFile('03.txt')])
.then(() => {
console.log('ALL FILES UNLINKED');
});
// OR YOU CAN USE promise.map
const filesToUnlink = ['01.txt', '02.txt', '3.txt'];
Promise.map(filesToUnlink, unlinkFile)
.then(() => {
console.log('ALL FILES UNLINKED');
});
Promise is not the only way. You can wrap your for loop in a function, and call that function from the unlink callback.
fs.unlink("FileName",function(err){
console.log("RUN");
loop();
})
function loop() {
for(let i = 0; i<10;i++) {
console.log(i);
}
}
On the other hand, with a Promise you would do this way :
new Promise((resolve, reject) => {
fs.unlink("FileName",function(err){
console.log("RUN");
return resolve();
});
})
.then(() => {
for(let i = 0; i<10;i++) {
console.log(i);
}
});
But, as #robertklep says, if your code does not need async, just call the synchronous function.
You should use Promise to get proper result.
let fs = require('fs');
new Promise((resolve,reject) => {
fs.unlink("demo1.txt", function (err) {
console.log("RUN");
if(err)
reject(err);
resolve();
})
})
.then(() => {
for (let i = 0; i < 10; i++) {
console.log(i);
}
},(error)=>{
console.log(error);
})
Well, I am lost in await and async hell. The code below is supposed to loop through a list of files, check if they exist and return back the ones that do exist. But I am getting a zero length list.
Node V8 code: caller:
await this.sourceList()
if (this.paths.length == 0) {
this.abort = true
return
}
Called Functions: (I took out stuff not relevant)
const testPath = util.promisify(fs.access)
class FMEjob {
constructor(root, inFiles, layerType, ticket) {
this.paths = []
this.config = global.app.settings.config
this.sourcePath = this.config.SourcePath
}
async sourceList() {
return await Promise.all(this.files.map(async (f) => {
let source = path.join(this.sourcePath, f.path)
return async () => {
if (await checkFile(source)) {
this.paths.push(source)
}
}
}))
}
async checkFile(path) {
let result = true
try {
await testPath(path, fs.constants.R_OK)
}
catch (err) {
this.errors++
result = false
logger.addLog('info', 'FMEjob.checkFile(): File Missing Error: %s', err.path)
}
return result
}
Your sourceList function is really weird. It returns a promise for an array of asynchronous functions, but it never calls those. Drop the arrow function wrapper.
Also I recommend to never mutate instance properties inside async methods, that'll cause insane bugs when multiple methods are executed concurrently.
this.paths = await this.sourceList()
if (this.abort = (this.paths.length == 0)) {
return
}
async sourceList() {
let paths = []
await Promise.all(this.files.map(async (f) => {
const source = path.join(this.sourcePath, f.path)
// no function here, no return here!
if (await this.checkFile(source)) {
paths.push(source)
}
}))
return paths
}
async checkFile(path) {
try {
await testPath(path, fs.constants.R_OK)
return true
} catch (err) {
logger.addLog('info', 'FMEjob.checkFile(): File Missing Error: %s', err.path)
this.errors++ // questionable as well - better let `sourceList` count these
}
return false
}