How to avoid propagation when chaining Promise with Non-Promise calls - javascript

I want the following code not to call OK logic, nor reject the promise. Note, I've a mixture of promise and non-promise calls (which somehow still managed to stay thenable after returning a string from its non-promise step), I just want the promise to stay at pending status if p1 resolves to non-OK value.
const p1 = new Promise((resolve, reject) => {
resolve("NOT_OK_BUT_NOT_A_CATCH_NEITHER");
});
p1.then(result => {
if (result !== "OK") {
return "How do I avoid calling OK logic w/out rejecting?";
}
else {
return Promise.resolve("OK");
}
}).then(result => {
console.error("OK logic...");
});

You've got two options:
1) throw an error:
p1.then(result => {
if (result =='notOk') {
throw new Error('not ok');
} else {
return 'OK';
}
})
.then(r => {
// r will be 'OK'
})
.catch(e => {
// e will be an error with message 'not ok', if it threw
})
the second .then won't run, the .catch will.
2) decide what to do in the latter .then conditionally:
p1.then(result => {
if (result =='notOk') {
return 'not ok'
} else {
return 'OK';
}
})
.then(r => {
if (r === 'OK') {
// do stuff here for condition of OK
}
})
This works because the second .then takes as an argument whatever was returned by the previous .then (however if the previous .then returned a Promise, the second .then's argument will be whatever was asyncronously resolved)
Note: if you .catch a promise that errored, and you return THAT promise, the final promise WON'T have an error, because the .catch caught it.

The best approach is to not chain that way. Instead do this:
const p1 = new Promise((resolve, reject) => {
resolve("NOT_OK_BUT_NOT_A_CATCH_NEITHER");
});
p1.then(result => {
if (result !== "OK") {
return "How do I avoid calling OK logic w/out rejecting?";
} else {
return Promise.resolve("OK")
.then(result => {
console.error("OK logic...");
});
}
})
If you're writing a linear chain, that means you're saying that you want step by step execution, which isn't what you want in this case.
Alternatively, if your target platforms/build system support it, write it as an async function:
(async function() {
const result = await Promise.resolve "NOT_OK_BUT_NOT_A_CATCH_NEITHER");
if (result !== "OK") {
return "How do I avoid calling OK logic w/out rejecting?";
} else {
await Promise.resolve("OK");
console.error("OK logic...");
}
})();

Found a way, not sure how good is it but it works. The idea is to have it as promises all the way and just not resolve when not needed.
In my case it saves me a hassle with managing ignorable results without polluting result with the likes of processable flags.
const p1 = new Promise((resolve, reject) => {
resolve("NOT_OK_BUT_NOT_A_CATCH_NEITHER");
});
p1.then(result => {
return new Promise((resolve, reject) => {
if (result === "OK") {
resolve(result);
}
// OR do nothing
console.error("Just Do nothing");
});
}).then(result => {
console.error("OK logic...");
});

Related

Promise chain continues after rejection

I'm having trouble to properly catch an error/reject in a promise chain.
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
}).then(p2).catch(() => {
console.log("Caught p2");
}).then(p3).catch(() => {
console.log("Caught p3");
}).then(() => {
console.log("Final then");
});
When the promise is rejected, the following .then still gets executed. In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
P1
P2
Caught p2
P3
Final then
The rejection gets caught correctly, but why is "P3" logged after the catch?
What am I doing wrong?
To clarify #evolutionxbox, this is my expected result:
Promise.resolve().then(() => {
console.log("resolve #1");
return Promise.reject();
}).then(() => {
console.log("resolve #2");
return Promise.resolve();
}).then(() => {
console.log("resolve #3");
return Promise.resolve();
}).then(() => {
console.log("Final end");
}).catch(() => {
console.log("Caught");
});
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
Here is a synchronous equivalent of your code:
const f1 = () => {
console.log("F1");
};
const f2 = () => {
console.log("F2");
throw new Error();
};
const f3 = () => {
console.log("F3");
};
try {
f1();
} catch {
console.log("Caught f1");
}
try {
f2();
} catch {
console.log("Caught f2");
}
try {
f3();
} catch {
console.log("Caught f3");
}
console.log("Final code");
As you can see, that gives a matching result. Hopefully, looking at the synchronous code you would not be surprised why. In a try..catch you are allowed to attempt recovery. The idea is that the catch will stop the error propagation and you can hopefully continue further. Or if you do want to stop, you still have to explicitly throw again, for example:
doCode();
try {
makeCoffee();
} catch(err) {
if (err instanceof IAmATeapotError) {
//attempt recovery
makeTea();
} else {
//unrecoverable - log and re-throw
console.error("Fatal coffee related issue encountered", err);
throw err;
}
}
doCode();
This is also the purpose Promise#catch() serves - so you can attempt recovery or at least act when there was a problem. The idea is that after the .catch() you might be able to continue:
const orderPizza = (topping) =>
new Promise((resolve, reject) => {
if (topping === "pepperoni")
reject(new Error("No pepperoni available"));
else
resolve(`${topping} pizza`);
});
const makeToast = () => "toast";
const eat = food => console.log(`eating some ${food}`);
async function main() {
await orderPizza("cheese")
.catch(makeToast)
.then(eat);
console.log("-----");
await orderPizza("pepperoni")
.catch(makeToast)
.then(eat);
}
main();
In order to reject the promise chain from a .catch() you need to do something similar as a normal catch and fail at the error recovery by inducing another error. You can throw or return a rejected promise to that effect.
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
The second piece of code you show fails entirely after a reject because there are no other .catch()-es that are successful. It is basically similar to this synchronous code:
try {
console.log("log #1");
throw new Error();
console.log("log #2");
console.log("log #3");
console.log("Final end");
} catch {
console.log("Caught");
}
Thus if you do not want to recover early, you can also skip the .catch() instead of inducing another error.
Try this.
const p1 = (arg) => {
// Promise returns data in the respected arguments
return new Promise((resolve, reject) => {
// Data to be accessed through first argument.
resolve(arg);
});
};
const p2 = (arg) => {
return new Promise((resolve, reject) => {
// Data to be accessed through second argument.
reject(arg);
});
}
p1('p1').then(resolve => {
console.log(resolve + ' is handled with the resolve argument. So it is accessed with .then()');
}) // Since reject isn't configured to pass any data we don't use .catch()
p2('p2').catch(reject => {
console.log(reject + ' is handled with the reject argument. So it is accessed with .catch()');
}) // Since resolve ins't configured to pass any data we don't use .then()
// You would normally configure a Promise to return a value on with resolve, and access it with .then() when it completes a task successfully.
// .catch() would then be chained on to the end of .then() to handle errors when a task cannot be completed.
// Here is an example.
const p3 = () => {
return new Promise((resolve, reject) => {
var condition = true;
if (condition === true) {
resolve('P3');
} else {
reject('Promise failed!');
}
});
};
p3('p3').then(resolve => {
console.log(resolve);
}).catch(reject => {
console.log(reject);
})
You do not do anything wrong.
In your code you call the first promise p1. Then you write p1.catch(...).then(...).then(...).then(...). This is a chain which means that you should call then 3 times, because you called resolve method in the p1 promise (all these thens depend on the first promise).
When the promise is rejected, the following .then still gets executed.
Yes. Just to be accurate: the then and catch method calls are all executed synchronously (in one go), and so all promises involved are created in one go. It's the callbacks passed to these methods that execute asynchronously, as the relevant promises resolve (fullfill or reject).
In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
This is not the case. The promise that a catch returns can either fullfill or reject depending on what happens in the callback passed to it, and so the callbacks further down the chain will execute accordingly when that promise resolves.
The rejection gets caught correctly, but why is "P3" logged after the catch?
As in your case the catch callback returns undefined (it only performs a console.log), its promise fullfulls! By consequence, the chained then callback -- on that promise -- is executed... etc.
If you want to "stop"
If you want to keep the chain as it is, but wish to have a behaviour where a rejection leads to no further execution of then or catch callbacks, then don't resolve the associated promise:
const stop = new Promise(resolve => null);
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
return stop; // don't resolve
}).then(p2).catch(() => {
console.log("Caught p2");
return stop;
}).then(p3).catch(() => {
console.log("Caught p3");
return stop;
}).then(() => {
console.log("Final then");
});

Am I chaining Promises correctly or committing a sin?

I have not worked with Javascript in a long time, so now promises are a new concept to me. I have some operations requiring more than one asynchronous call but which I want to treat as a transaction where steps do not execute if the step before failed. Currently I chain promises by nesting and I want to return a promise to the caller.
After reading the chaining section of Mozilla's Using Promises guide, I'm not sure if what I'm doing is correct or equivalent to the "callback pyramid of doom".
Is there a cleaner way to do this (besides chaining with a guard check in each then)? Am I right in my belief that in Mozilla's example it will execute each chained then even when there is an error?
myfunction(key) => {
return new Promise((outerResolve, outerReject) => {
return new Promise((resolve, reject) => {
let item = cache.get(key);
if (item) {
resolve(item);
} else {
//we didnt have the row cached, load it from store
chrome.storage.sync.get(key, function (result) {
chrome.runtime.lastError
? reject({ error: chrome.runtime.lastError.message })
: resolve(result);
});
}
}).then((resolve) => {
//Now the inner most item is resolved, we are working in the 'outer' shell
if (resolve.error) {
outerReject(resolve);
} else {
//No error, continue
new Promise((resolve, reject) => {
chrome.storage.sync.get(keyBasedOnPreviousData, function (result) {
chrome.runtime.lastError
? reject({ error: chrome.runtime.lastError.message })
: resolve(result);
});
}).then((resolve) => {
//finally return the result to the caller
if (resolve.error) {
outerReject(resolve);
} else {
outerResolve(resolve);
}
});
}
});
});
}
Subsequent then statements are not executed (until a catch) when an exception is thrown. Also, .then returns a Promise, so you don't need to create an additional, outer Promise.
Try this example:
var p = new Promise((resolve, reject) => {
console.log('first promise, resolves');
resolve();
})
.then(() => {
throw new Error('Something failed');
})
.then(() => {
console.log('then after the error');
return('result');
});
p.then(res => console.log('success: ' + res), err => console.log('error: ' + err));
You will not see "then after the error" in the console, because that happens after an exception is thrown. But if you comment the throw statement, you will get the result you expect in the Promise.
I am not sure I understand your example entirely, but I think it could be simplified like this:
myfunction(key) => {
return new Promise((resolve, reject) => {
let item = cache.get(key);
if (item) {
resolve(item);
} else {
//we didnt have the row cached, load it from store
chrome.storage.sync.get(key, function (result) {
chrome.runtime.lastError
? throw new Error(chrome.runtime.lastError.message)
: resolve(result);
});
}
}).then((previousData) => {
// keyBasedOnPreviousData is calculated based on previousData
chrome.storage.sync.get(keyBasedOnPreviousData, function (result) {
chrome.runtime.lastError
? throw new Error(chrome.runtime.lastError.message)
: return result;
});
});
}
It's a bit of a mess. This is my attempt at rewriting. A good thing to try to avoid is new Promise().
function chromeStorageGet(key) {
return new Promise( (res, rej) => {
chrome.storage.sync.get(key, result => {
if (chrome.runtime.lastError) {
rej(new Error(chrome.runtime.lastError.message))
} else {
res(result)
}
});
});
});
function myfunction(key) {
const item = cache.get(key) ? Promise.resolve(cache.get(key)) : chromeStorageGet(key);
return item.then( cacheResult => {
return chromeStorageGet(keyBasedOnPreviousData);
});
}
Why avoid new Promise()?
The reason for this is that you want to do every step with then(). If any error happened in any of the promises, every promise in the chain will fail and any subsequent then() will not get executed until there is a catch() handler.
Lots of promise based-code requires no error handlers, because promise-based functions always return promises and exceptions should flow all the back to the caller until there is something useful to be done with error handling.
Note that the exceptions to these 2 rules are in my chromeStorageGet function. A few notes here:
new Promise can be a quick and easy way to convert callback code to promise code.
It's usually a good idea to just create a little conversion layer for this callback-based code. If you need chrome.storage.sync in other places, maybe create a little utility that promisifies all its functions.
If there is only 1 'flow', you can just use a series of then() to complete the process, but sometimes you need to conditionally do other things. Just splitting up these complicated operations in a number of different functions can really help here.
But this:
const result = condition ? Promise.resolve() : Promise.reject();
Is almost always preferred to:
const result = new Promise( (res, rej) => {
if (condition) {
res();
} else {
rej();
}
}

promise execution with condition

I have a following promise function
function run(args) {
return new Promise(function(resolve, reject) { //logic });
}
I want to pass an argument to the promise function run and on the returned value I want to have some condition, if it satisfies I want to pass one set of argument to the promise and if it fails the other set of result and on the basis of the above promise result a last promise with the consolidated values from the above promise as argument.
run(a)
.then(() =>{
if (condition) {
run(b)
.then(() => {
return something
})
}
else
run(c)
.then(() => {
return something
})
})
.then(rows => {
use the something returned
})
Something like above
Is it possible. Also what if both the condition is not satisfied how to handle the error ?
or is there any better way to do it ?
That's two questions, really, but oh well...
Conditionally returning one of two different promises
Basically, in the first .then(), you have to return a promise. You do so by adding return before run(b) and run(c). This will make the second .then() pick up the value of something.
.then(() => {
if (condition) {
return run(b).then(() => {
return something;
});
} else {
return run(c).then(() => {
return something;
});
}
})
Error throwing and handling
Your scenario is a simple if-else. One of the two will always be fulfilled, so there is no "if no condition is satisfied". However, let us assume you have two conditions instead, and one must be satisfied in order for the entire thing to be considered fulfilled. All you need to do is throw an Error:
.then(() => {
if (condition1) {
return run(b);
} else if (condition2) {
return run(c);
}
throw new Error("Neither condition was true.");
})
.then(rows => {
// use the something returned
})
.catch(err => console.log(err));
The thrown Error object will be received as argument err by the final .catch().
There are a bunch of different ways to handle this, but here is one.
then((data) => {
let nextCall;
if ( data === "Something") {
nextCall = somePromiseFunction(data);
} else {
nextCall = someOtherPromiseFunction(data);
}
return nextCall;
})
.then((data) => { ... })

How to properly use resolve and reject for promises

I've started to look at using Promises and have begun by putting together a simple function and calling it a few times. I need a sanity check around reject and resolve.
Is this the correct way to "promisify" a function?
Is this the correct way to deal with reject and resolve?
Anything I've got totally wrong?
const Redis = require('ioredis');
const redis = new Redis({
port: 6379,
host: '127.0.0.1'
});
function checkValues(name, section) {
return new Promise((resolve, reject) => {
redis.multi()
.sismember('names', name)
.sismember('sections', section)
.exec()
.then((results) => {
if(results[0][1] === 1 && results [1][1] ===1) {
reject('Match on both.');
} else if(results[0][1] === 1 || results [1][1] ===1) {
reject('Match on one.');
} else {
redis.multi()
.sadd('names', name)
.sadd('sections', section)
.exec()
.then((results) => {
// Lazy assumption of success.
resolve('Added as no matches.');
})
// No catch needed as this would be thrown up and caught?
}
})
.catch((error) => {
console.log(error);
});
});
}
// Call stuff.
checkValues('barry', 'green')
.then((result) => {
// Added as no matches "resolve" message from 'barry', 'green'
console.log(result);
retutn checkValues('steve', 'blue');
})
.then((result) => {
// Added as no matches "resolve" message from 'steve', 'blue'
retutn checkValues('steve', 'blue');
})
.then((result) => {
// Match on both "reject" message from 'steve', 'blue'
console.log(result);
})
.catch((error) => {
console.log(error);
});
No, this is kind of an anti-pattern. You already have a function that returns a promise so you don't need to wrap it in another promise, you can just return it. Remember that then() returns a promise that resolves to the return value of then. You can also return another promise from then. Usually this looks super clean, but in this case you need some logic in the then function, so it gets a little messy.
function checkValues(name, section) {
// Just return this Promise
return redis.multi()
.sismember('names', name)
.sismember('sections', section)
.exec()
.then((results) => {
if(results[0][1] === 1 && results [1][1] ===1) {
// Rejections will be caught down the line
return Promise.reject('Match on both.');
} else if(results[0][1] === 1 || results [1][1] ===1) {
return Promise.reject('Match on one.');
} else {
// You can return another Promise from then()
return redis.multi()
.sadd('names', name)
.sadd('sections', section)
.exec()
}
})
// You don't need to catch here - you can catch everything at the end of the chain
}
Several points:
Don't use the explicit-promise-construction-antipattern
As a general guide in purging the anti-pattern, after removing the new Promise() wrapper, change resolve statements to return and reject statements to throw new Error(...).
.catch() catches! If errors are to be observable/handleable by the caller, then either don't catch in checkValues() or catch and re-throw. Catching without re-throwing will cause the returned promise to settle on its success path, never its error path, which is great for error recovery but not always appropriate.
Suggest that all three cases, 'Match on both', 'Match on one' and 'Added as no matches', are really successes. Unless there's a particular reason for wanting 'Match on both' and 'Match on one' to be seen as error conditions, then return rather than reject/throw. That way, your call stuff chain will progress down its success path, .then().then().then(), regardless of expected outcome; only an unexpected error will go down the error path to be caught by the final .catch(). This isn't a general rule; very often, throwing is the right thing to do, but not here.
function checkValues(name, section) {
return redis.multi()
.sismember('names', name)
.sismember('sections', section)
.exec()
.then((results) => {
if(results[0][1] === 1 && results [1][1] === 1) {
return 'Match on both.';
} else if(results[0][1] === 1 || results [1][1] ===1) {
return 'Match on one.';
} else {
return redis.multi()
.sadd('names', name)
.sadd('sections', section)
.exec()
.then((results) => {
return 'Added as no matches.';
});
}
})
.catch((error) => {
console.log(error);
throw error;
});
}
// Call stuff.
checkValues('barry', 'green')
.then((result) => {
console.log(result); // expect 'Added as no matches'
return checkValues('steve', 'blue');
})
.then((result) => {
return checkValues('steve', 'blue'); // expect 'Added as no matches'
})
.then((result) => {
console.log(result); // expect 'Match on both'
})
.catch((error) => {
// only an unexpected error will land here
console.log(error);
});

Extract .then and .catch from promises

I have written a general .js crud object which holds all the methods used to communicate with the server. However I'd like to avoid the repetition of .then and .catch and I would like to abstract that functionality in an external method.
Not sure if what I'm trying to achieve is even possible.
My code below:
all(url, success, fail){
return new Promise((resolve,reject) => {
_get(url)
.then((response) => {
if (response.status == 200) {
success.call(this,response);
return resolve();
}
})
.catch((error) => {
fail.call(this, error);
reject(error);
});
});}, submit, update .....
Wonderland desired result:
all(url, success, fail){
return new Promise((resolve, reject) => {
_get(url).handle(args);
});
}
Just avoid the Promise constructor antipattern and callbacks, and you'll be good!
function all(url) {
return _get(url).then((response) => {
if (response.status == 200) {
return response;
}
// most likely you want to `throw` an error here?
});
}

Categories

Resources