How to send value from Promise 'then' to 'catch'? - javascript

I just want to ask how should I pass the resolve promise to catch if the value on the resolve is not intended.
e.g.
let prom = getPromise();
prom.then(value => {
if (value.notIWant) {
// Send to catch <-- my question is here, I want to pass it on the catch.
}
// Process data.
}).catch(err => {
// Pass the error through ipc using json, for logging.
});
I tried to using throw but the object cant be parsed to json and just got an empty object.
ANSWER:
#BohdanKhodakivskyi first comment below is the answer I want.
#31py answer is also correct but the #BohdanKhodakivskyi solution is much simplier and will render the same result.

Simply use throw value;. In your case:
prom.then(value => {
if (value.notIWant) {
// Send to catch
throw value;
}
// Process data.
}).catch(err => {
// Pass the error through ipc using json, for logging.
});
Please also note the difference and limitations between using Promise.reject() and throw which is perfectly described in this question. For example, throw will not work in some async scenarios.

You can simply return a rejected promise:
prom.then(value => {
if (value.notIWant) {
return Promise.reject('your custom error or object');
}
// Process data.
}).catch(err => {
console.log(err); // prints 'your custom error or object'
});
.catch actually handles any promise rejection in the chain, so if you're returning a rejected promise, the control automatically flows to catch.

why you just not rethrow the error? throw new Error("something");

You can use outside functions to do it:
var processData = function(data) {
// process data here
}
var logIt = function(data) {
// do logging here..
}
let prom = getPromise();
prom.then(value => {
if (value.notIWant) {
// Send to catch <-- my question is here, I want to pass it on the catch.
logIt(/*pass any thing*/);
}
// Process data.
processData(data);
}).catch(err => {
logIt(/*pass any thing*/);
});

Related

JavaScript Promise : Catching errors within chained functions

I have a function that is used to add a record to the IndexDb database:
async function addAsync(storeName, object) {
return new Promise((res, rej) => {
// openDatabaseAsync() is another reusable method to open the db. That works fine.
openDatabaseAsync().then(db => {
var store = openObjectStore(db, storeName, 'readwrite');
var addResult = store.add(JSON.parse(object));
addResult.onsuccess = res;
addResult.onerror = (e) => {
console.log("addResult Error");
throw e;
};
}).catch(e => {
// Error from "throw e;" above NOT GETTING CAUGHT HERE!
console.error("addAsync ERROR > ", e, storeName, object);
rej(e);
});
})
}
If I try to add a duplicate key, then I expect:
addResult.onerror = (e) => {
console.log("addResult Error");
throw e;
}
to capture that. It does.
But then, I also expect my
.catch(e => {
// Error from "throw e;" above NOT GETTING CAUGHT HERE!
console.error("addAsync ERROR > ", e, storeName, object);
rej(e);
})
to catch that error. But instead I get an "uncaught" log.
Console output:
addResult Error
Uncaught Event {isTrusted: true, type: "error", target: IDBRequest, currentTarget: IDBRequest, eventPhase: 2, …}
Does that final .catch only handle exceptions from the openDatabaseAsync call? I would have thought now as it is chained to the .then.
In summary, here's what I would expect from the above code:
If openDatabaseAsync() fails then I'm not catching that so the error would be sent to the caller of addAsync().
If .then fails then I expect the .catch to catch it, log the error and then reject the promise meaning that the called of addAsync() would need to handle that.
However, I would have thought that I should get the log from the line:
console.error("addAsync ERROR > ", e, storeName, object);
before the reject is sent back to the caller of addAsync(), which may be unhandled at that point.
Your approach would benefit form a larger overhaul.
Generally, don't write a function as async when it's not also using await.
Don't use new Promise() for an operation that returns a promise, such as openDatabaseAsync() does. Return that promise, or switch to async/await.
It would be useful to wrap IndexedDB operations so that they follow promise semantics.
On the example of IDBRequest:
function promisifyIDBRequest(idbr) {
return new Promise( (resolve, reject) => {
idbr.onsuccess = () => resolve(idbr.result);
idbr.onerror = (e) => reject(e.target.error);
});
}
Now you can do this:
async function addAsync(storeName, object) {
const db = await openDatabaseAsync();
const store = openObjectStore(db, storeName, 'readwrite');
return promisifyIDBRequest(store.add(JSON.parse(object)));
}
Add a try/catch block if you want to handle errors inside of addAsync().
It's worth checking out existing solutions that wrap the entire IndexedDB interface with promise semantics, such as https://github.com/jakearchibald/idb.
FWIW, the promise-chain variant of the above function would look like this:
function addAsync(storeName, object) {
return openDatabaseAsync().then( (db) => {
const store = openObjectStore(db, storeName, 'readwrite');
return promisifyIDBRequest(store.add(JSON.parse(object)));
});
}
both variants return a promise for an IDBRequest result.

Asserting on catch block code inside forEach loop

I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});

Method to stop an Await from executing if there's an empty Object from Promise but shouldn't return an error and execute other tasks

So what I want to achieve is that when a function returns an empty Object from the Promise, the Await function must not be executed and the rest of the Application must carry on executing other tasks. As the object that is returned maybe not always be available but should be returned when available.
function getData(Data) : Promise<Object> {
return new Promise((resolve, reject) => {
request({
// Method
}, (err, resp, file)=> {
if (err) {
reject(err);
} else {
resolve({
// Return object infomation
});
}
});
});
}
let someData = await Promise.all(data.map(getData));
// This should have a part that ignores if getData is empty and this await function ignored.
The rest of the application should be able to run as normal. I have tried to use:
.catch(error => { });
But didn't work the way I wanted it to work
There may be a better way, but what solved my issue was to pass an empty array to the data
if (isNullOrUndefined(data)) {
data = [];
}
In this way the await function now works the way I want it to work and does not throw error:
TypeError: Cannot read property 'map' of undefined

How to get the second `then` callback parameter with async/await?

For example
randomLibPromise.then((data, err) => { // do something with err })
how would I translate that with await?
This promise comes from a library so I don't have control over the fact that the error goes through then instead of catch. What I have is:
let data = await randomLibPromise.catch(err)
but I don't get that second parameter and I can't retrieve the error.
Every example I find on Google talk about catch for error handling but not then.
To "fix" the weird promise, you could just throw err if there is one, and return the data otherwise:
var fixed = randomLibPromise.then((data, err) => {
if(err) throw err;
return data;
})
The result will be a promise which does the correct thing, i.e. passes the data to a then, an error to a catch or, if awaiting, either returns data or throws the error.
fixed.then(data => { /* handle data */ }).catch(err => { /* handle err */ });
// or
try {
var data = await fixed;
// handle data
} catch(err) {
//handle err
}

How to call a function inside bluebirdjs promise

I am trying to get a promise function with bluebirdjs. but all attempts falling my way because maybe I don't know what I am doing...?
I want to get files locations, then download the files one after the other then push to an array.
import * as Promise from 'bluebird';
fileFunction(files){
Promise.map(files, function(file) {
// Promise.map awaits for returned promises as well.
//Promise.delay(1000);
return this.getLocation(file);
},
).then((file) => {
console.log('done')
});
}
getLocation(file){
if(file){
return this._storage.ref(file).getDownloadURL().subscribe(url =>
this.img_array.push(url)
);
}
When I call the return this.getLocation(file)... I get the following error
bluebird.js:1545 Unhandled rejection TypeError: Cannot read property 'getLocation' of undefined ....bluebird.js
Edit part of the code am using now!
fileFunction(files){
return Promise.map(files, file => {
return this.getDownloadUrl(file);
}).then(locations => {
// add these locations onto this.img_array
this.img_array.push(...locations);
console.log('done');
return locations;
});
}
getFiles(e): Promise<any>{
this.outPutFiles = e;
this.fileFunction(this.outPutFiles).then(locations => {
locations.map((arr) => arr.subscribe((files) => this.downloadUrls.push(files)));
}).catch(err => {
console.log(err);
});
}
getDownloadUrl(file){
if(file){
return this._storage.ref(file).getDownloadURL();
} else {
return Promise.reject(new Error('No file passed to getLocation'));
}
}
this.getLocation(file) does not work because you've lost the value of this because you're inside a Promise.map() callback. Remember, that every normal function call in Javascript changes the value of this unless you specifically control the value of this.
You can fix that part of the issue with a simple arrow function for your callback like this:
fileFunction(files){
return Promise.map(files, file => {
return this.getLocation(file);
}).then(locations => {
// add these locations onto this.img_array
this.img_array.push(...locations);
console.log('done');
return locations;
});
}
This assumes that this.getLocation(file) returns a promise that resolves to the location value. Are you sure it does that? It looks like there may be more to your problem than just that first error you ran into.
And, after a side conversation, you also need to fix getLocation() to return a promise that resolves to the desired URL. Looking in the firebase Javascript doc, it appears that getDownloadURL() already returns a promise that resolves to the desired URL. So, you can just return that promise and let Promise.map() manage the results for you.
getLocation(file){
if(file){
return this._storage.ref(file).getDownloadURL();
} else {
return Promise.reject(new Error("No file passed to getLocation"));
}
}
And, then you would use it all like this:
obj.fileFunction(fileArray).then(locations => {
console.log(locations);
}).catch(err => {
console.log(err);
});

Categories