How to do conditional promise chaining - javascript

I'm learning promises/typescript/angular and I want to chain promises conditionally.
This is the actual state of my method:
private executePromiseModificationEvenement<T>(edition: Models.CalendrierParametresModelEdition, modeCreation: boolean, deferred: ng.IDeferred<T>) {
var promise: ng.IPromise<Object>;
//Step1
if (modeCreation) {
promise = this.$calendrier.Actions.enregistrerEvenementOutlook(edition);
} else {
promise = this.$calendrier.Actions.modifierEvenementOutlook(edition);
}
if (this.$scope.outlook) {
promise.then((x) => {
if (x != '') edition.idOutlook = x.toString();;
return deferred.resolve();
}, (x) => {
return deferred.reject();
});
} else {
//Step2
promise.then((x) => {
if (x != '') edition.idOutlook = x.toString();
return this.$calendrier.Actions.modifierEvenement(edition);
}, (x) => {
//Ajout MessageBox message error
return this.$calendrier.Actions.modifierEvenement(edition);
})
//Step3
.then((x) => {
if (edition.opportunite != null) this.$rootScope.$broadcast("pushEchangeOpportunite", { idOpportunite: parseInt(edition.opportunite), action: 2, IdContact: edition.id, Libelle: edition.title, StartDate: moment(edition.start).toDate() });
return deferred.resolve();
}, (x) => {
return deferred.reject();
});
}
}
I'm familiar of async/await of C#, neither of which gives a problem with conditional chaining, but I'm having trouble achieving the same with promises.
Is it to correct to put a .then not just after the creation of the promise but after an if ?
Is it possible that the .then may never be called because the promise is already finished?

It is fine to chain promises together in any order or use ifs, loops, whatever.
If you call .then on a resolved promise then it will execute instantly, so that's fine too.
The only way the then will not be called is if the promise chain is never resolved or is rejected.
A normal way of chaining might be to return the next object from within the function. This tends to be neater than calling deferred.resolve().
E.g.
var promise = this.$calendrier.Actions.enregistrerEvenementOutlook(edition);
promise = promise.then(function (x) {
return 2 * x;
})
promise = promise.then(function (x) {
return 2 * x;
})
or
var promise =
this.$calendrier.Actions.enregistrerEvenementOutlook(edition)
.then(function (x) {
return 2 * x;
})
.then(function (x) {
return 2 * x;
})

executePromiseModificationEvenement() doesn't need a Deferred. There's absolutely no value in passing one in. Instead, you should be looking to return the promise returned by a promise chain formed within the function. The caller function(s) will need only a minor change.
Straightforwardly, your function can be rewritten with a series of (conditional) promise = promise.then(...) statements with a final return promise. Some of the code repetition can also be addressed.
private executePromiseModificationEvenement<T>(edition: Models.CalendrierParametresModelEdition, modeCreation: boolean<T>) {
var promise: ng.IPromise<Object>;
promise = modeCreation ?
this.$calendrier.Actions.enregistrerEvenementOutlook(edition) :
this.$calendrier.Actions.modifierEvenementOutlook(edition);
promise = promise.then((x) => {
if (x != '') {
edition.idOutlook = x.toString();
}
});
if (!this.$scope.outlook) {
promise = promise.then(() => {
return this.$calendrier.Actions.modifierEvenement(edition);
}, () => {
return this.$calendrier.Actions.modifierEvenement(edition);
})
.then((x) => {
if (edition.opportunite != null) {
this.$rootScope.$broadcast("pushEchangeOpportunite", {
idOpportunite: parseInt(edition.opportunite),
action: 2,
IdContact: edition.id,
Libelle: edition.title,
StartDate: moment(edition.start).toDate()
});
}
});
}
return promise;
}
However, that may not be the best solution.
It may be more appropriate to perform the if(this.$scope.outlook) test at the point where this.$calendrier.Actions.modifierEvenement()... is called, during chain settlement rather than during the chain building phase. The result will not necessarily be the same because this.$scope.outlook will have had an opportunity to change state.
Personally, I would guess that performing the test later is more appropriate (or inconsequential). If so, the promise chain can be built unconditionally and all the tests performed internally, which if nothing else, is much tidier.
private executePromiseModificationEvenement<T>(edition: Models.CalendrierParametresModelEdition, modeCreation: boolean<T>) {
return (modeCreation ?
this.$calendrier.Actions.enregistrerEvenementOutlook(edition) :
this.$calendrier.Actions.modifierEvenementOutlook(edition))
.then((x) => {
if (x != '') {
edition.idOutlook = x.toString();
}
})
.catch((x) => { return x; }) // this mid-chain-error-recovery line is rather odd but consistent with the original code. It may be better placed one step earlier.
.then(() => {
if (!this.$scope.outlook) {
return this.$calendrier.Actions.modifierEvenement(edition)
.then(() => {
if (edition.opportunite != null) {
this.$rootScope.$broadcast("pushEchangeOpportunite", {
'idOpportunite': parseInt(edition.opportunite),
'action': 2,
'IdContact': edition.id,
'Libelle': edition.title,
'StartDate': moment(edition.start).toDate()
});
}
});
}
});
}

Related

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

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...");
});

Simple Promise and Then implementation

Recently I was shown a piece of code that were asked during a full-stack developer interview.
It involved creating a Promise, in which the candidate should implement,
passing it a resolve function, and chaining 2 then's.
I tried implementing the Promise very naively only to make the code work.
Created a ctor that accepts a resolver func,
Created a Then function that accepts a callback and returns a Promise,
and simply calls the callback on the resolver function.
class MyPromise {
constructor(resolver) {
this.resolver = resolver;
}
then(callback) {
const result = new MyPromise(callback);
this.resolver(callback);
return result;
}
}
promise = new MyPromise(
(result) => {
setTimeout(result(2), 500);
});
promise.then(result => {
console.log(result);
return 2 * result;
}).then(result => console.log(result));
The expected result is 2,4 - just like operating with real Promise.
But i'm getting 2,2.
I'm having trouble figuring out how to get the return value for the first "then" and passing it along.
Here's the shortened code for creating a promise class,
class MyPromise {
constructor(executor) {
this.callbacks = [];
const resolve = res => {
for (const { callback } of this.callbacks) {
callback(res);
}
};
executor(resolve);
}
then(callback) {
return new MyPromise((resolve) => {
const done = res => {
resolve(callback(res));
};
this.callbacks.push({ callback: done });
});
}
}
promise = new MyPromise((resolve) => {
setTimeout(() => resolve(2), 1000);
});
promise.then(result => {
console.log(result);
return 2 * result;
}).then(result => console.log(result));
Your question has some issues:
The r2 variable is nowhere defined. I will assume result was intended.
The setTimeout is doing nothing useful, since you execute result(2) immediately. I will assume setTimeout(() => result(2), 500) was intended.
If the code was really given like that in the interview, then it would be your job to point out these two issues before doing anything else.
One issue with your attempt is that the promise returned by the then method (i.e. result) is never resolved. You need to resolve it as soon as the this promise is resolved, with the value returned by the then callback.
Also, the promise constructor argument is a function that should be executed immediately.
In the following solution, several simplifications are made compared to a correct Promise behaviour.
It does not call the then callbacks asynchronously;
It does not support multiple then calls on the same promise;
It does not provide the rejection path;
It does not prevent a promise from resolving twice with a different value;
It does not deal with the special case where a then callback returns a promise
console.log("Wait for it...");
class MyPromise {
constructor(executor) {
executor(result => this.resolve(result));
}
resolve(value) {
this.value = value;
this.broadcast();
}
then(onFulfilled) {
const promise = new MyPromise(() => null);
this.onFulfilled = onFulfilled;
this.resolver = (result) => promise.resolve(result);
this.broadcast();
return promise;
}
broadcast() {
if (this.onFulfilled && "value" in this) this.resolver(this.onFulfilled(this.value));
}
};
// Code provided by interviewer, including two corrections
promise = new MyPromise(
(result) => {
setTimeout(()=>result(2), 500); // don't execute result(2) immediately
});
promise.then(result => {
console.log(result); // Changed r2 to result.
return 2 * result;
}).then(result => console.log(result));
Note the 500ms delay in the output, which is what should be expected from the (corrected) setTimeout code.
I posted a full Promises/A+ compliant promise implementation with comments in this answer
How about very simple:
const SimplePromise = function(cb) {
cb(
data =>
(this.data = data) &&
(this.thenCb || []).forEach(chain => (this.data = chain(this.data))),
error =>
(this.error = error) &&
(this.catchCb || []).forEach(chain => (this.error = chain(this.error)))
);
this.then = thenCb =>
(this.thenCb = [...(this.thenCb || []), thenCb]) && this;
this.catch = catchCb =>
(this.catchCb = [...(this.catchCb || []), catchCb]) && this;
};
Example Here: https://codesandbox.io/s/0q1qr8mpxn
Implying that this r2 is actually the result parameter.
The problem with your code is that you do not retrieve the result from the result(2). The first "then" gets executed, prints 2, return 4, but this 4 is just wasted.
I wrote some code with synchronous function only to demonstrate what to do if you want to get this 2,4 output:
class MyPromise {
constructor(resolver) {
this.resolver = resolver;
}
then(callback) {
var res = callback(this.resolver());
var result = new MyPromise(() => { return res; });
return result;
}
}
let promise = new MyPromise(
() => {
return 2;
});
promise
.then(result => {
console.log(result);
return 2 * result;
})
.then(result => {
console.log(result);
});
If you want the resolver to be somewhat async you should use Promises (because return value from function executed inside setTimeout can be retrieved, see
here.
If you are not allowed to use these built-in Promises, you can write them yourself with some dummy deferred object and await them (setInterval) to resolve (should be basically the same logic).
There are a few problems with your original code. Notably you are only executing the constructor argument upon call of the then method and you aren't actually chaining the outputs of the 'then' callbacks.
Here is a very (very!) basic promise implementation based upon adapting your example. It will also work for cases where 'then' is called after the promise is resolved (but not if 'then' is already called - multiple then blocks is not supported).
class MyPromise {
constructor(resolver) {
let thisPromise = this;
let resolveFn = function(value){
thisPromise.value = value;
thisPromise.resolved = true;
if(typeof thisPromise.thenResolve === "function"){
thisPromise.thenResolve();
}
}
if (typeof resolver === "function") {
resolver(resolveFn);
}
}
then(callback) {
let thisPromise = this;
thisPromise.thenFn = callback;
return new MyPromise((resolve) =>{
thisPromise.thenResolve = () => {
thisPromise.value = thisPromise.thenFn(thisPromise.value);
resolve(thisPromise.value);
}
//automatically resolve our intermediary promise if
//the parent promise is already resolved
if(thisPromise.resolved){
thisPromise.thenResolve();
}
});
}
};
//test code
console.log("Waiting for Godot...");
promise = new MyPromise((resolve) =>{
setTimeout(()=>{
resolve(2)
},500);
});
promise.then((result) => {
console.log(result);
return 2 * result;
}).then((result) => {
console.log(result);
return 2 * result;
}).then((result) => {
console.log(result)
});

Returning value from multiple promises within Meteor.method

After a bunch of looking into Futures, Promises, wrapAsync, I still have no idea how to fix this issue
I have this method, which takes an array of images, sends it to Google Cloud Vision for logo detection, and then pushes all detected images with logos into an array, where I try to return in my method.
Meteor.methods({
getLogos(images){
var logosArray = [];
images.forEach((image, index) => {
client
.logoDetection(image)
.then(results => {
const logos = results[0].logoAnnotations;
if(logos != ''){
logos.forEach(logo => logosArray.push(logo.description));
}
})
});
return logosArray;
},
});
However, when the method is called from the client:
Meteor.call('getLogos', images, function(error, response) {
console.log(response);
});
the empty array is always returned, and understandably so as the method returned logosArray before Google finished processing all of them and returning the results.
How to handle such a case?
With Meteor methods you can actually simply "return a Promise", and the Server method will internally wait for the Promise to resolve before sending the result to the Client:
Meteor.methods({
getLogos(images) {
return client
.logoDetection(images[0]) // Example with only 1 external async call
.then(results => {
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}); // `then` returns a Promise that resolves with the return value
// of its success callback
}
});
You can also convert the Promise syntax to async / await. By doing so, you no longer explicitly return a Promise (but under the hood it is still the case), but "wait" for the Promise to resolve within your method code.
Meteor.methods({
async getLogos(images) {
const results = await client.logoDetection(images[0]);
const logos = results[0].logoAnnotations;
if (logos != '') {
return logos.map(logo => logo.description);
}
}
});
Once you understand this concept, then you can step into handling several async operations and return the result once all of them have completed. For that, you can simply use Promise.all:
Meteor.methods({
getLogos(images) {
var promises = [];
images.forEach(image => {
// Accumulate the Promises in an array.
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
return Promise.all(promises)
// If you want to merge all the resulting arrays...
.then(resultPerImage => resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, [])); // Initial accumulator value.
}
});
or with async/await:
Meteor.methods({
async getLogos(images) {
var promises = [];
images.forEach(image => {
// DO NOT await here for each individual Promise, or you will chain
// your execution instead of executing them in parallel
promises.push(client.logoDetection(image).then(results => {
const logos = results[0].logoAnnotations;
return (logos != '') ? logos.map(logo => logo.description) : [];
}));
});
// Now we can await for the Promise.all.
const resultPerImage = await Promise.all(promises);
return resultPerImage.reduce((accumulator, imageLogosDescriptions) => {
return accumulator.concat(imageLogosDescriptions);
}, []);
}
});

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) => { ... })

Node Unhandled Promise issue

I previously asked the question found here:
Retaining Data across multiple promise chains
I ended up using T.J. Crowder's answer for my base code and have made many many changes since. But I noticed something weird in node which I cannot seem to overcome. I went back to the base code he provided and the issue seems to be there as well.
Here is the example:
"use strict";
// For tracking our status
class Status {
constructor(total = 0, count = 0) {
this.id = ++Status.id;
this.total = total;
this.count = count;
}
addCall() {
++this.total;
return this;
}
addProgress() {
++this.count;
return this;
}
toString() {
return `[S${this.id}]: Total: ${this.total}, Count: ${this.count}`;
}
}
Status.id = 0;
// The promise subclass
class RepoPromise extends Promise {
constructor(executor) {
super(executor);
this.s = new Status();
}
// Utility method to wrap `then`/`catch` callbacks so we hook into when they're called
_wrapCallbacks(...callbacks) {
return callbacks.filter(c => c).map(c => value => this._handleCallback(c, value));
}
// Utility method for when the callback should be called: We track that we've seen
// the call then execute the callback
_handleCallback(callback, value) {
this.s.addProgress();
console.log("Progress: " + this.s);
return callback(value);
}
// Standard `then`, but overridden so we track what's going on, including copying
// our status object to the new promise before returning it
then(onResolved, onRejected) {
this.s.addCall();
console.log("Added: " + this.s);
const newPromise = super.then(...this._wrapCallbacks(onResolved, onRejected));
newPromise.s = this.s;
return newPromise;
}
// Standard `catch`, doing the same things as `then`
catch(onRejected) {
this.s.addCall();
console.log("Added: " + this.s);
const newPromise = super.catch(...this._wrapCallbacks(onRejected));
newPromise.s = this.s;
return newPromise;
}
}
// Create a promise we'll resolve after a random timeout
function delayedGratification() {
return new Promise(resolve => {
setTimeout(_ => {
resolve();
}, Math.random() * 1000);
});
}
// Run! Note we follow both kinds of paths: Chain and diverge:
const rp = RepoPromise.resolve('Test');
rp.then(function(scope) {
return new Promise((resolve, reject) => {
console.log(' Rejected')
reject(scope)
})
})
.catch(e => {console.log('Never Makes it')})
when I run this with: node test.js I get the following output
Added: [S1]: Total: 1, Count: 0
Added: [S1]: Total: 2, Count: 0
Added: [S1]: Total: 3, Count: 0
Progress: [S1]: Total: 3, Count: 1
Rejected
(node:29364) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Test
(node:29364) [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.
Notice that the console log for "never makes it" is not present, also note, I already fixed the issue with catch running twice as it is simple syntactic sugar for then(null, function(){}), so you can ignore that.
Why is catch not working as I expect? When I do this with a normal promise, there are no issues, like below. So I know for a fact that the _wrapCallbacks is causing the issue, I am just not sure why, or how to fix it.
const rp = Promise.resolve('Test');
rp.then(function(scope) {
return new Promise((resolve, reject) => {
console.log(' Rejected')
reject(scope)
})
})
.catch(e => {console.log('Makes it')})
The catch implementation of your promise doesn't work. Notice that the native catch is implemented as return this.then(null, callback) - calling super.catch will just direct back to your then implementation.
And your then implementation has a major fault: it doesn't like to get a null argument before a function. Observe what happens in the above call when you're doing this:
_wrapCallbacks(...callbacks) {
return callbacks.filter(c => c).map(…);
// ^^^^^^^^^^^^^^^
}
then(onResolved, onRejected) {
…
const newPromise = super.then(...this._wrapCallbacks(onResolved, onRejected));
…
}
That'll simply remove the null from the array of arguments and pass the onrejected callback as onfulfilled instead. You'll want to drop the filter and use a ternary in the mapping function instead:
_wrapCallbacks(...callbacks) {
return callbacks.map(c => typeof c == "function"
? value => this._handleCallback(c, value)
: c);
}
Also you can just drop the overridden catch.

Categories

Resources