Hi everyone!
I use Node.js v8.5.0.
And this is my code in Node.js (simplified):
// function that returns promise
const request = (url) =>
new Promise((resolve, reject) => {
superagent
.get(url)
.end((err, res) => {
if (err || !res.ok) {
reject(err);
return;
}
resolve(res);
})
})
// function that add .then and .catch to promise
const to = promise =>
promise
.then((data) => [null, data])
.catch((err) => [err]);
// making request
let [err, result] = await to(request());
When i do request and some error occurs .catch function doesnt catch rejected value and i get error like Unhandled Promise Rejection. But, in fact I added .catch function to promise.
Does anyone know what is wrong here?
Thanks for help!
A superagent request already returns a promise so use that instead
const request = (url) => superagent.get(url).then(res=> {/*transform res**/ return res})
The code you provided does not have that behavior but I am familiar with the uncaught in promise warning.
If you want to catch an error of a promise later you have to catch it first and return the promise before you catch it.
The following will give you uncaught in promise warning:
var failPromise = val => Promise.reject(val);
var to = async promise => promise.then(val=>[null,val]).catch(err=>[err]);
var p = failPromise(88);
//later you will catch the error
setTimeout(()=>to(p).then(val=>console.log("resolved to:",val)),100);
But if you change it a bit then you won't
var failPromise = val => {
const p = Promise.reject(val);
p.catch(ignore=>ignore);
return p;//return the promise that did not have the catch
};
var to = async promise => promise.then(val=>[null,val]).catch(err=>[err]);
var p = failPromise(88);
//later you will catch the error
setTimeout(()=>to(p).then(val=>console.log("resolved to:",val)),100);
As suggested before; your request method should just return the promise that superagent returns but in such a way that you can catch the reject later without the errors(warnings):
const request = (url) => {
const p = superagent
.get(url);
p.catch(ignore=>ignore);
return p;
})
If you are wondering why you get uncaught in promise; it is because you catch the rejection in the queue and not in the stack. Stack and queue is explained here.
The example that catches in stack causes no errors in the console but then returns the promise that did not have the catch on it. Depending how far in the queue you are going to catch it it may still give you errors but in code provided it won't.
Related
I am trying to force a rejection for a promise.allSettled() function in a controlled way.
The idea is run a series of urls in batches through an API, this API from time to time returns a 500 error for a given request and it can be safetly retried. So I want to trigger a rejection on promise.allSettled() where I can collect the failing urls and later on rerun then on a recursion.
Batchrequest function
export async function batchRequest(poolLimit, array, iteratorFn, exception) {
const promises = []
const racers = new Set()
for (const item of array) {
const pro = Promise.resolve().then(() => iteratorFn(item, array))
promises.push(pro)
racers.add(pro)
const clean = () => racers.delete(pro)
pro.then(clean).catch(clean)
if (racers.size >= poolLimit) await Promise.race(racers)
}
const results = await Promise.allSettled(promises)
// Collect errors rejected by iteratorFn,
const rejected = results
.filter(({ status, reason }) => status === 'rejected' && reason.name === exception)
.map(({ reason }) => reason.error)
// Recurse the array of rejected urls
if (rejected.length) {
await batchRequest(poolLimit, rejected, iteratorFn, exception)
}
}
Here we run the promises as normal but collect all rejected urls, I am trying to use the exception 'timeout' as the rule to determine if it needs to be rerun as it was just a timeout error.
Iterator function
async function runRequest(url) {
try {
const { data } = await axios('https://exampleAPI.com')
// Take the data and write it somewhere...
} catch (error) {
if (error.response.status === 500) {
throw { name: 'timeout', url }
}
}
})
const urls = [...many urls]
await batchRequest(100, urls, runRequest, 'timeout')
I am getting an error saying
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(). The promise rejected with the reason "#<Object>".] { code: 'ERR_UNHANDLED_REJECTION' }
How can I force a controlled rejection on promise.allSettled()?
UPDATE-----
I found that the unhandled rejection was at the point in which I started the batchrequest
await batchRequest(100, urls, runRequest, 'timeout')
It needs a try catch here:
const urls = [...many urls]
try {
await batchRequest(100, urls, runRequest, 'timeout')
} catch (error) {
console.log(error)
}
but the whole point was to use the promise.allSettled() to absorb the error and not get out of the batchrequest
It looks to me like you're not handling the exception thrown by the runRequest or the first call to iteratorFn. That's what's causing the "unhandled exception" error.
I'd suggest to run this code through a debugger and go line by line until you find the line causing you to get that exception thrown.
Just manually handle each promise and collect errors and results and return or throw when all promises resolved:
const errors = [];
const results = [];
const promises = [
Promise.resolve(1),
Promise.resolve(2),
Promise.reject("error1"),
Promise.reject("error2"),
Promise.resolve(3),
];
Promise.all(
promises.map(p => p.then(r => results.push(r)).catch(e => errors.push(e)))
).then(() => {
console.log({results, errors});
});
I am using await to make the code cleaner, but I am not sure whether I am handling exceptions correctly.
An example while using azure-devops-node-api;
const foo = async() => {
return new Promise((resolve, reject) => {
...
...
const teams = await coreApiObject.getTeams(currProject.id)
.catch(err => { reject(err) return })
...
...
})
}
In this code I am assuming, if there is a problem with promise call, foo() is going to return reject.
async functions always return a promise, so you don't need to explicitly create one yourself. Any non-promise value returned from an async function is implicitly wrapped in a promise.
Inside the foo function, you just need to await the call coreApiObject.getTeams(...) and to catch and handle any error, use the try-catch block.
Your code can be simplified as shown below:
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (e) {
// handle error
}
}
If you want to the calling code to handle the error, then you can use one of the following options:
Remove the try-catch block and just return the result of coreApiObject.getTeams(...).
const foo = async() => {
return coreApiObject.getTeams(currProject.id);
}
Removing the try-catch block and just returning the call to coreApiObject.getTeams(...) will allow the calling code to handle the error because the promise returned by the foo function will get resolved to the promise returned by coreApiObject.getTeams(...); this means that the fate of the promise returned by the foo function will depend on whatever happens to the promise returned by coreApiObject.getTeams(...).
If the promise returned by coreApiObject.getTeams(...) is rejected, promise returned by the foo function will also be rejected and hence the calling code will have a change to catch the promise rejection and handle it.
Throw the error from the catch block.
const foo = async() => {
try {
const teams = await coreApiObject.getTeams(currProject.id);
return teams;
} catch (error) {
// throw the error
throw error;
}
}
Other option is to throw the error from the catch block to make sure that the promise returned by the async function is rejected.
If you don't throw the error or return a promise or a thenable that is rejected, returning any other value from the catch block will fulfil the promise returned by the async function with whatever value is returned inside the catch block.
I have some fairly simple code, and I can't figure out what I'm doing wrong:
try {
service.cache.client = await initCache();
} catch (e) {
console.log(e);
}
and
const initCache = async () => {
const options = {
url: my_URL,
port: my_PORT + 4
};
const client = await getClient(options);
client.on('connect', (a, b) => {
logger.info(
`Successfully connected: ${my_URL}:${my_PORT}`
);
client.connect = true;
});
client.on('error', (err) => Promise.reject());
return client;
};
EDIT: I should add that in my example above, my_URL is a bogus URL that will never connect. I'm trying to write some code so the app uses alternate methods when the chache client is unavailable.
No matter what I do, I cannot escape Unhandled Rejection warnings. The code above results in error: Unhandled Rejection at: Promise. If I pass a new error to Promise.reject, I get the same thing. If I throw my own error in the catch block, I still get error: Unhandled Rejection at: Promise. What do I have to do to actually handle this rejection?
EDIT: if I change client.on to client.on('error', (err) => new Error(err));, the Unhandled Promise message goes away. But I'm unable to catch this error in the catch block; a simple console.log isn't happening.
When using callbacks with promises you have to do a little bit of work to make them work nice together. If I understand your intention you want to wait for the client to connect to return the promise, and in case of the event error you want to throw an error.
You have to return a new Promise like so:
const initCache = async () => {
const options = {
url: my_URL,
port: my_PORT + 4,
};
const client = await getClient(options);
return new Promise((success, reject) => {
client.on("connect", (a, b) => {
logger.info(`Successfully connected: ${my_URL}:${my_PORT}`);
client.connect = true;
success(client);
});
client.on("error", (err) => reject(err));
});
};
The following is valid:
new Promise<void>((resolve, reject) => {
reject()
})
.then(() => {})
.catch(() => {})
But I might not always care about the error. Is there a way to make the catch optional?
I tried this but it didn't work:
new Promise<void>((resolve, reject?) => {
if (reject) reject()
})
.then(() => {})
Error: Uncaught (in promise): undefined
Is there a way to make the catch optional?
No. If you are using a promise that might error, you need to handle that (or propagate it to your caller).
Of course if you create the promise yourself, rejecting it is optional, and you can choose to never reject your promises so that you won't need to handle any errors. But if there are errors from promises that you are using, and you want to ignore them, you must do so explicitly. Just write
somePromise.catch(e => void e);
// or () => { /* ignore */ }
// or function ignore() {}
I was trying to solve the same issue, and finally come up with the following promise wrapper:
/**
* wraps a given promise in a new promise with a default onRejected function,
* that handles the promise rejection if not other onRejected handler is provided.
*
* #param customPromise Promise to wrap
* #param defaultOnRejected Default onRejected function
* #returns wrapped promise
*/
export function promiseWithDefaultOnRejected(customPromise: Promise<any>, defaultOnRejected: (_: any) => any): Promise<any> {
let hasCatch = false;
function chain(promise: Promise<any>) {
const newPromise: Promise<any> = new Promise((res, rej) => {
return promise.then(
res,
function(value) {
if (hasCatch) {
rej(value);
} else {
defaultOnRejected(value);
}
},
);
});
const originalThen = newPromise.then;
// Using `defineProperty` to not overwrite `Promise.prototype.then`
Object.defineProperty(newPromise, 'then', {
value: function (onfulfilled: any, onrejected: any) {
const result: Promise<any> = originalThen.call(newPromise, onfulfilled, onrejected);
if (typeof onrejected === 'function') {
hasCatch = true;
return result;
} else {
return chain(result);
}
}
});
return newPromise;
}
return chain(customPromise);
}
This function lets you wrap your promises with a defaultOnRejected function that will handle the rejected promise if no other handler is provided. For example:
const dontCare = promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {});
The result promise will never throw an "Unhandled Promise Rejection", and you can use it as follows:
dontCare.then(x=>console.log("never happens")).catch(x=>console.log("happens"));
or
dontCare.then(x=>console.log("never happens"), x=>console.log("happens"));
or simply without onRejected handler:
dontCare.then(x=>console.log("never happens")).then(x=>console.log("also never happens"));
An issue with this util is that it is not working as expected with async/await syntax: you need to propagate and handle the "catch" path as follows:
async () => {
try {
await promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {})
.catch((e) => { throw e; });
} catch (e) {
console.log("happens");
}
}
You could resolve when the error is something you don't care about. If your catch returns anything other than a rejected promise, the error isn't propagated down the chain.
const ignorableError = new Error("I don't care about this error");
const myPromise = new Promise((resolve, reject) => {
reject(ignorableError);
})
.then(() => {})
.catch(error => {
if(error == ignorableError) {
console.log("Error ignored");
return;
}
// Do something else...
});
myPromise.then(() => console.log("Success"))
Let me try to describe your situation:
You have a service that gets user information and a function called getUser that uses that service. When the service fails for any reason then getUser does not have a user available. The result of getUser is used quite a lot of times in your code with the following situation(s):
User is available run a function (block of code).
User is not available run a function (block of code).
Run a function with the error/reject of the service.
When using getUser result you may want to run all 3 functions, a combination of 2 of them or only one.
Current getUser returns a Promise and this type does not seem to be suitable for your situation. Mainly because rejecting a promise and not catching it will cause unhandled promise rejection. And because if you want to run code if user is available or not it complicates the functions (they both have to check the result instead of assuming user is or is not available).
Maybe you can try the following code, please be careful making assumptions in the not available block, this could be due to any error. For example: it does not mean the user does not exist because it could be a network error.
In the example getUser is used but can be any function that returns a promise where you assume not available on reject.
const isAvailable = promise => {
//do not expose NOT_AVAILABLE outside this function
const NOT_AVAILABLE = {type:"not available"};
//no uncaught promise rejection errors by VM
const savePromise = promise.catch(x=>x);
return {
available:fn=>
promise
.catch(e=>Promise.reject(NOT_AVAILABLE))
.then(fn)
.catch(
e=>
(e===NOT_AVAILABLE)
? undefined//ignore
: Promise.reject(e)//re throw, error is not from service
),
//call not available with the promise if promise rejects
notAvailable:fn=>promise.catch(()=>fn(promise)),
catchError:promise.catch.bind(promise)
};
}
const service = arg =>
(arg===1)
? Promise.resolve(arg)
: Promise.reject(arg)
const getUser = arg => isAvailable(service(arg));
var user = getUser(2);
//if service failed available will be skipped
user.available(
user=>console.log("skipped:",user)
);
//both catch and notAvailable will be called
user.notAvailable(
arg=>console.log("not available:",arg)
);
user.notAvailable(
arg=>console.log("still not available:",arg)
);
//not handling catchError does not cause uncaught promise exception
// but you can inspect the error
// user.catchError(
// err=>console.log("error is::",err)
// );
var user = getUser(1);
//you can call available on user multiple times
user.available(
user=>console.log("got user:",user)
);
user.available(
user=>Promise.resolve(user)
.then(user=>console.log("still got user:",user))
.then(x=>Promise.reject("have to catch this one though"))
.catch(e=>console.log("ok, got error trying to run available block:",e))
);
//showing you can inspect the error
var user = getUser(5);
user.catchError(err=>console.log("error from service:",err));
Consider this function:
aPromise = require('axios');
function middleware(callback) {
axios.get('/api/get')
.then(callback)
.catch(callback);
}
Consider this test:
const callback = (err) => {
expect(isError(err)).toBe(true);
done();
};
middleware(callback);
The isError is a lodash function.
Consider aPromise as something I want to test. If the promise always resolves, this test should not pass. But it will! And that's because the promise's catch actually catches the expect exception.
My question is: How to not catch the error in a promise's catch handler when expect throws an error in the promise's then handler?
Note that I don't use async/await.
You need to create a failed promise and you need to return the promise in your test. Please have a look on the doc on testing promises.
aPromise = require('axios');
jest.mock('axios', () => {
get: ()=> jest.fn() //initialy mock the get function
})
it('catch failing promises',() = > {
const result = Promise.reject('someError'); //create a rejected promises
aPromise.get.mockImplementation(() => result)// let `get` return the rejected promise
const callback = jest.fn()
middleware(callback)
return result
.then (()=>{
expect(callback).toHaveBeenCalledWith('someError');
})
})