This function is being called by a parent function, that should return the success object, or the error.
I'm having trouble using async while trying to retry a function 4 times. On success it's all good. On error, I'm getting "Unhandled Promise Rejection".
//create record
const createSFDCRecord = async (object, userData) => {
console.log("trial", retryCount)
return await conn.sobject(object).create(userData, async (err, ret) => {
if (err || !ret.success) {
if (retryCount < retryLimit) {
setTimeout(async () => {
return await createSFDCRecord(object, userData)
}, 2000)
retryCount++
} else {
pagerDutyEvent(
`Failed to send ${userData.Trigger_Code_kcrm__c} form data into ${object} after 5x`,
"error",
err
)
}
}
console.log(`Created ${object} id : ${ret.id}`)
})
}
sobject is from jsforce
http://jsforce.github.io/jsforce/doc/SObject.html
You are mixing promises with callbacks:
sobject(object).create returns a promise, so there is no need to use the callback argument.
return with a value has no sense in a setTimeout callback. That returned value is going nowhere.
The promises resulting from the repeated attempts are not chained: the resolution of the first is not locked into the next one. Instead you have a stand alone promise in a setTimeout callback, whose resolution or rejection is not handled.
The "Unhandled Promise Rejection" happens because you have an await of a promise that can reject, yet you do not capture the exception that will be triggered at that line when this happens.
You should refactor this to something like this (untested code):
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const createSFDCRecord = async (object, userData) => {
let retryCount = 1;
while (true) {
console.log("trial", retryCount);
try {
let ret = await conn.sobject(object).create(userData);
if (!ret.success) throw new Error("ret.success is false");
console.log(`Created ${object} id : ${ret.id}`);
return ret.id;
} catch(err) {
if (retryCount >= retryLimit) {
pagerDutyEvent(
`Failed to send ${userData.Trigger_Code_kcrm__c} form data into ${object} after ${retryLimit}x`,
"error",
err
);
return -1; // Or throw an exception... but then deal with it.
}
}
retryCount++;
await delay(2000);
}
}
Related
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)
Is this possible way to return resolve or reject message from one function to another?
As I am writing to pass resolve message in postman whenever my task is completed or reject message when there is some error
But after after writing return it still not returning the resolve message or reject message inside Postman
any idea how this can be resolve?
async function readFile(filePath) {}
async function getAllFile(filePath) {
const paths = await readFile(filePath);
}
async function filterFiles(filePath) {
const paths = await getAllFile(filePath);
}
function addDocument(childProduct){
return new Promise((resolve, reject) => {
Document.create({
name: childProduct,
},
}).then(function (filePath) {
filterFiles(filePath);
let msg = "Document created Succesfully";
return resolve(msg);
})
.catch(function (err) {
return reject("Can't be updated please try again :) " + err);
});
});
}
function updateDoc(data){
return new Promise((resolve, reject) => {
Document.update({
name: data.name,
}
where: {
product_id: data,
},
})
}).then(function (childProduct) {
addDocument(childProduct);
let msg = "Updated Successfully";
return resolve(msg);
})
.catch(function (err) {
return reject("Can't be updated please try again :) " + err);
});
}
Product.findOne and Document.findAll return a Promise, so they can be returned and awaited directly.
You can chain await func1(); await func2(); await func3() in one try{} block, and catch any error that happens in one place :
const filterFiles = async filePath => {
const paths = await getAllFiles(filePath);
// .. Do something else here
return paths // This is a Promise because async functions always return a Promise
}
const findOneDoc = name => Product.findOne({ where: { name } }); // This func returns a Promise
const findAllDocs = product_id => Document.findAll({ // This func returns a Promise too
raw: true,
where: { product_id }
});
(async () => {
try {
const childProduct = await findOneDoc("some_name");
console.log("All good until now!");
const filePath = await findAllDocs(childProduct._id);
console.log("Still good");
const filteredFiles = await filterFiles(filePath);
console.log("All went well.");
console.log(filteredFiles);
} catch (err) {
// If any of the functions above fails, the try{} block will break and the error will be caught here.
console.log(`Error!`, err);
}
})();
There are few things I would like to mention.
When you create a promise, it should have resolve() and reject() inside it.
for ex-
function testPromise() {
return new Promise((resolve, reject) => {
// your logic
// The followin if-else is not nessesary, its just for an illustration
if (Success condition met) {
resolve(object you want to return);
}else {
reject(error);
// you can add error message in this error as well
}
});
}
// Calling the method with await
let obj = await testPromise()
// OR call with then, but its better to go with await
testPromise().then((obj)=>{
// Access obj here
})
In the method which you have written, You have applied .then() method to non promise object. You have to complete the promise block first with resolve() and reject() inside it. Then you can return that promise from a function, use it in async function Or apply .then() block on it.
Also you don't need to add return statement to resolve() and reject() statement. The system will take care of it.
You can also use try catch block inside a promise. Its better to write reject() statement in catch block, if anything goes wrong.
The problem is that i am getting UNhandledPromiseRejection error eveen though i think i have handled all the cases. The code flows from profileRoutes to Controller to Utils where the error comes first.
Inside the profileRoutes.js
router.get('/:username', async (r, s) => {
try{
let profileData = await getProfileData(r.params.username);
s.json({ success: true, payload: profileData });
}catch(err){
console.log('ending request processing by responding a error');
s.status(500).json({ success: false, message: 'err[0].message' });
}
});
Inside the controllers/index.js
const fetchQueue = [getUserRepos];
async function getProfileData(username) {
let profileData = {};
try{
let results = await Promise.all(fetchQueue.map(item => item(username)));
for (let i = 0; i < results.length; i++) {
profileData[getKeys[i]] = results[i];
}
return profileData;
}catch(err){
console.log('error log in controller/index getProfileData function');
throw err;
}
}
const getUserRepos = async (username) => {
try {
// const res = await utils.gqlSender(username, 'userRepos', { createdAt });
const res = await utils.gqlSender(username, 'userReposData');
return res.user.repositories;
} catch (err) {
console.log('error log in controller/index getUserRepos function');
throw err;
}
};
Inside the utils/index.js
const gqlSender = async (username, type, opt = {}) => {
axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if(res.data.errors) { // this is where the error is recieved and so i reject promise.
console.log('bef###re');
return Promise.reject (res.data.errors);
}
console.log('###',res.data);
return res.data;
}).catch(err => {
console.log('error in making axios request inside utils/index gqlSender function');
throw err;
// return Promise.reject(err);
});
The stack trace on making get request to /:username is-
error log in controller/index getUserRepos function
error log in controller/index getProfileData function
ending request processing by responding a error
bef###re
error in making axios request inside utils/index gqlSender function
(node:11260) UnhandledPromiseRejectionWarning: [object Array]
(node:11260) 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: 2)
(node:11260) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
I dont think i am missing any Promise Rejection.
Any help is appreciated. Thanks.
i have referred these answers previously -
What's the difference between returning value or Promise.resolve from then()
Do I need to return after early resolve/reject?
Your gqlSender function is not returning the promise that will get rejected, so it is not handled anywhere. You should write either
const gqlSender = (username, type, opt = {}) => {
return axios.post('', {
// ^^^^^^
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
});
};
or
const gqlSender = async (username, type, opt = {}) => {
// ^^^^^
const res = await axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
});
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
}
I am using Promise.prototype.finally() (or try-catch-finally in an async function) in my production code to execute some follow-up code without changing resolution/rejection status of the current promise.
However, in my Jest tests, I would like to detect that the Promise inside finally block wasn't rejected.
edit: But I don't want to actually await the Promise in my "production" code (there I care only about errors re-thrown from catch, but not about errors from finally).
How can I test for that? Or at least how to mock the Promise.prototype to reject the current promise on exceptions from finally?
E.g. if I would be testing redux action creators, the tests pass even though there is a message about an unhandled Promise rejection:
https://codesandbox.io/s/reverent-dijkstra-nbcno?file=/src/index.test.js
test("finally", async () => {
const actions = await dispatchMock(add("forgottenParent", { a: 1 }));
const newState = actions.reduce(reducer, undefined);
expect(newState).toEqual({});
});
const dispatchMock = async thunk => {...};
// ----- simplified "production" code -----
const reducer = (state = {}, action) => state;
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
dispatch(get(parentId)); // tests pass if the promise here is rejected
}
};
const get = id => async dispatch => {
dispatch("get start");
try {
await someFetch(id);
dispatch("get success");
} catch (e) {
dispatch("get failed");
throw e;
}
};
const someFetch = async id => {
if (id === "forgottenParent") {
throw new Error("imagine I forgot to mock this request");
}
Promise.resolve(id);
};
dispatch(get(parentId)); // tests pass if an exception is thrown here
There is no exception throw in that line. get(parentId) might return a rejected promise (or a pending promise that will get rejected later), but that's not an exception and won't affect control flow.
You might be looking for
const add = parentId => async dispatch => {
dispatch("add start");
try {
await someFetch("someData");
dispatch("add success");
} catch (e) {
dispatch("add failed");
throw e;
} finally {
await dispatch(get(parentId));
// ^^^^^
}
};
Notice that throwing exceptions from a finally block is not exactly a best practice though.
edit: more general solutions available on https://stackoverflow.com/a/58634792/1176601
It is possible to store the Promise in a variable accessible in some helper function that is used only for the tests, e.g.:
export const _getPromiseFromFinallyInTests = () => _promiseFromFinally
let _promiseFromFinally
const add = parentId => async dispatch => {
...
} finally {
// not awaited here because I don't want to change the current Promise
_promiseFromFinally = dispatch(get(parentId));
}
};
and update the test to await the test-only Promise:
test("finally", async () => {
...
// but I want to fail the test if the Promise from finally is rejected
await _getPromiseFromFinallyInTests()
});
I asked question about how to run asynchronous method in loop. And I am now doing it in something like recursion.
How can asynchronous method in loop executed in sequence?
But now I can't get data pass to final callback(last line of my code), if db data is fetched successfully at second time.
I notice the problem may be: second CallGetDB resolve() the data I want but first CallGetDB doesn't get that. Then first CallGetDB finished wihtout resolve anything. But I don't know how to solve it.
function CallGetDB(UID) {
return new Promise(function(resolve, reject){
GetDynamodb(UID)
.then((data)=> {
console.log("check before if"+JSON.stringify(data));
dbResponse = data;
resolve(dbResponse);
}
)
.catch((data)=> {
if(index++ < 2)
{
console.log("This is "+(index+1)+" try to get data from db with UID.");
setTimeout(function(){CallGetDB(UID);},100);
}
else
{
console.log("Database multi fetch failed");
resolve("Database multi fetch failed");
}
});
});
SendSQS(UID,event).then((data)=>{
return CallGetDB(data);
}).then((data)=>{
console.log("I am at most out:" +JSON.stringify(data));
response.body=JSON.stringify(data);
callback(null,response);
});
Wrapping GetDynamodb(UID) in a new Promise is an anti-pattern since it returns a promise.
Following adds a retries parameter with a default to CallGetDB() and either returns a new promise in the catch() when retries are within limits.... or throws a new error to get caught in following catch()
let sleep = ms => new Promise(r => setTimeout(r, ms));
function CallGetDB(UID, retries = 0) {
return GetDynamodb(UID)
.catch((data) => {
if (retries++ < 2) {
console.log("This is " + (retries + 1) + " try to get data from db with UID.");
// return another promise
return sleep(100).then(() => CallGetDB(UID, retries));
} else {
console.log("Database multi fetch failed");
// throw error to next catch()
throw new Error("Database multi fetch failed");
}
});
}
SendSQS(UID, event).then((data) => {
return CallGetDB(data);
}).then((data) => {
console.log("Data Success:" + JSON.stringify(data));
response.body = JSON.stringify(data);
callback(null, data);
}).catch(err => /* do something when it all fails*/ );
Your promise isn't resolving in the event of an error and an index lower than 2. Here:
if(index++ < 2){
console.log("This is "+(index+1)+" try to get data from db with UID.");
setTimeout(function(){CallGetDB(UID);},100);
}
At that point, your promise will never fulfill or reject as the original promise never gets fulfilled and can't resolve the JSON data nor does it hit your else branch.It becomes an unresolved promise (a promise stalled indefinitely). It will work in the event that GetDynamodb fulfills on the first attempt though.
You can fix this by fulfilling the promise inside the if branch:
setTimeout(function(){resolve(CallGetDB(UID));},100);
That being said, you probably shouldn't be wrapping promises like this. This is a somewhat similar approach to yours:
let delay = ms => new Promise(r => setTimeout(r, ms));
function CallGetDB(UID) {
return GetDynamodb(UID).then(data => {
console.log("check before if"+JSON.stringify(data));
return data;
}).catch(err => {
if(index++ < 2){
console.log("This is "+(index+1)+" try to get data from db with UID.");
return delay(100).then(() => CallGetDB(UID));
} else {
console.log("Database multi fetch failed");
return "Database multi fetch failed";
}
});
});
You can also use a closure for scoping retries, so you have a proper scope for your index variable:
let delay = r => new Promise(r => setTimeout(r, ms));
function CallGetDB(retries) {
let index = retries;
return function inner(UID){
return getDynamodb(UID).then((data)=> {
console.log("check before if"+JSON.stringify(data));
return data;
}).catch(err => {
if(index--){
console.log("This is "+(retries-index)+" try to get data from db with UID.");
return delay(100).then(() => inner(UID));
} else {
console.log("Database multi fetch failed");
return "Database multi fetch failed";
}
});
};
}
Which you can now use like: CallGetDB(2)(data)