Promise reject not working inside of callback - javascript

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

Related

Promise Reject Callback is not called but Resolve Callback is being called as expected

So I have a problem regarding Promises currently and I'm not sure why it is not working as expected.
In my Angular Library I have the following code:
public async load() {
return new Promise<void>((resolve, reject) => {
this.service.isReady$
.pipe(
take(2),
filter((result) => result.ready),
switchMap(() => {
return this.loadClassifierFiles();
}),
tap(() => {
console.log('Classifier Files should load now');
})
)
.subscribe({
next: () => {
try {
// do stuff here ...
resolve();
} catch {
reject(new Error('Could not load all Classifier Files'));
}
},
error: () => {
reject(
new Error('Could not fetch all Classifier Files from Destination')
);
},
});
});
}
In my Application I have the following code:
public async changeSomething() {
await this.service
.changeStuff()
.then(() => {
this.modelStatus = ModelChangeStatus.SUCCESS;
})
.catch((error) => {
this.modelStatus = ModelChangeStatus.FAILURE;
});
}
In the test of my Library I have verified that for example on an error when loading the files during this.loadClassifierFiles() I will get into the error block of my subscription and therefore the Promise should be rejected. However, only when I resolve the Promise the callback in my Application is called. When I reject the Promise only the error handling of the library is being called but not the catch block in my application that I need for a status update.
I also tried the following but nothing changed:
this.openCvService
.changeStuff(tempConfig)
.then(
() => {
this.modelStatus = ModelChangeStatus.SUCCESS;
},
(error: Error) => {
console.log(error.message);
this.modelStatus = ModelChangeStatus.FAILURE;
}
)
Any ideas why the rejection callback is not being called?

How to call an API twice if there is an error occurred?

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)

Jest mocks and error handling - Jest test skips the "catch" of my function

I'm creating a jest test to test if metrics were logged for the error handling of the superFetch function. My approach is creating a mock function for retryFetch and returning a Promise reject event. I expect that to go to the superFetch catch but it keeps ending up in superFetch then. What can I do to handle my errors in superFetch catch?
These are the functions:
// file: fetches.js
export function retryFetch(url) {
return new Promise((resolve, reject) => {
fetch(url).then(response => {
if (response.ok) {
resolve(response);
return;
}
throw new Error();
}).catch(error => {
createSomething(error).then(createSomething => {
reject(createSomething);
});
return;
});
});
});
export function superFetch(url, name, page) {
return retryFetch(url)
.then(response => {
return response;
}).catch(error => {
Metrics.logErrorMetric(name, page);
throw error;
});
}
My jest test:
import * as fetch from '../../src/utils/fetches';
describe('Fetch fails', () => {
beforeEach(() => {
fetch.retryFetch = jest.fn(() => Promise.reject(new Error('Error')));
});
it('error metric is logged', () => {
return fetch.superFetch('url', 'metric', 'page').then(data => {
expect(data).toEqual(null);
// received data is {"ok": true};
// why is it even going here? im expecting it to go skip this and go to catch
}).catch(error => {
// this is completely skipped. but I'm expecting this to catch an error
// received error is null, metric was not called
expect(Metrics.logErrorMetric).toHaveBeenCalled();
expect(error).toEqual('Error');
});
});
});
The problem is that you overwrite the function in the exported module but superFetch use the original one inside of the module, so the overwrite will have no effect.
You could mock fetch directly like this:
global.fetch = jest.mock(()=> Promise.reject())

Promise Chaining error handling

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;
}

Promise gets rejected even though there is no error. NodeJs

So, I have a piece of code that makes use of Promises and I am using the bluebird library.
The problem is that my promise gets rejected even if there is no error. It completely skips the .then block even for a simple console.log
Here is the code that makes use of promise:
function returnMeetings(query) {
return new Promise((reject, resolve) => {
Meeting.find(query, (err, foundMeetings) => {
if (err) {
console.log("We have a error")
reject(err);
}
resolve(foundMeetings);
})
})
}
And here is the code that makes use of that returnMeetings function
returnMeetings(query)
.then((data) => {
console.log("here we are")
// while (count < stopAt) {
// let localData = [];
// if (req.params.duration === 'monthly') {
// let {
// date1,
// date2
// } = twoDates(count, count);
// localData = data.filter((el) => {
// if (el.startDate) {
// let localDate = new Date(el.startDate);
// if (localDate >= date1 && localDate <= date2) {
// return el;
// }
// }
// })
// if (localData) {
// data.push({
// data: localData,
// month: count
// })
// }
//
// if (count === stopAt - 1) {
// myEmitter.emit('found all meetings')
// } else {
// count++;
// }
// }
// }
}).catch((err) => {
res.status(500).json({
message: err
})
})
As you can see in the returnMeetings function i have placed a console.log inside the if(err) block, and it never runs, from that I conclude that I didn't receive any error from the mongoose query.
Still, the code completely skips the .then block, and falls into the catch chain. I have a simple console.log inside the then block, and an interesting thing to note is that the value of err in the callback inside catch block is an array of mongodb documents.
Can someone explain, why is my code behaving in this manner?
Thanks for your help.
the order of resolve/reject in your Promise constructor callback is wrong ... the names of the functions are irrelevant, the first is to resolve, the second is to reject ... you are actually rejecting the promise when you call resolve
i.e. you could
return new Promise((fred, wilma) => {
then to resolve the promise, you would call fred(value) and to reject you would call wilma(error)
in other words, the names you give the callback arguments is irrelevant, the position is relevant
change your code as follows:
function returnMeetings(query) {
return new Promise((resolve, reject) => {
Meeting.find(query, (err, foundMeetings) => {
if (err) {
console.log("We have a error")
reject(err);
}
resolve(foundMeetings);
})
})
}

Categories

Resources