Given some number of functions, returning promises:
function foo(arg) {
return new Promise(function(resolve, reject) {
if (stuff(arg)) {
resolve('result from foo');
} else {
resolve(null);
}
});
);
// ... maybe more of these functions ...
function bar(arg) {
return new Promise(function(resolve, reject) {
if (otherStuff(arg)) {
resolve('result from bar');
} else {
resolve(null);
}
});
);
How can we iterate through the functions in a serial fashion, short circuiting after the first function returning a non-null value?
[
foo,
// ...
bar
].firstWithArg('some arg')
.then(function(result) {
// result: 'result from ___', or `null`
});
Essentially, the desired behaviour is:
new Promise(function(resolve, reject){
foo('some-arg')
.then(function(result) {
if (result) {
resolve(result);
} else {
// ...
bar('some-arg')
.then(function(result) {
if (result) {
resolve(result);
} else {
resolve(null); // no functions left
}
})
}
});
});
Promise.race() can't be used, as the functions can't all be fired. They must be executed serially, stopping after the first success.
You've said your first question is really just setup for the second, which is the real question.
So I think your question is: How do you execute a series of functions that return promises serially, short-circuiting when the first one resolves with a non-null
value?
I probably wouldn't, I'd use reject
rather than resolve(null)
(but in a comment you've clarified you want resolve(null)
, and I see your point; I cover that below):
function foo(arg) {
return new Promise(function(resolve, reject) {
if (stuff(arg)) {
resolve('result from foo');
} else {
reject(); // <=== Note
}
});
}
// ... maybe more of these functions ...
function bar(arg) {
return new Promise(function(resolve, reject) {
if (otherStuff(arg)) {
resolve('result from bar');
} else {
reject(); // <=== Note
}
});
}
Then you use catch
to handle rejections up until you get back a resolution:
foo("a")
.catch(() => bar(1))
.catch(() => foo("b"))
.catch(() => bar(2))
.catch(() => foo("c"))
.catch(() => bar(3))
.then(value => {
console.log("Done", value);
});
function otherStuff(arg) {
return arg == 2;
}
function stuff(arg) {
return arg == "c";
}
function foo(arg) {
console.log("foo:", arg);
return new Promise(function(resolve, reject) {
if (stuff(arg)) {
console.log("foo:", arg, "resolving");
resolve('result from foo');
} else {
console.log("foo:", arg, "rejecting");
reject(); // <=== Note
}
});
}
// ... maybe more of these functions ...
function bar(arg) {
console.log("bar:", arg);
return new Promise(function(resolve, reject) {
if (otherStuff(arg)) {
console.log("bar:", arg, "resolving");
resolve('result from bar');
} else {
console.log("bar:", arg, "rejecting");
reject(); // <=== Note
}
});
}
foo("a")
.catch(() => bar(1))
.catch(() => foo("b"))
.catch(() => bar(2))
.catch(() => foo("c"))
.catch(() => bar(3))
.then(value => {
console.log("Done", value);
});
That works because resolutions bypass the catch
handlers, so the subsequent functions are never called.
If you have an array of functions to call, there's an idiom for it: Array#reduce
:
let functions = [
() => foo("a"),
() => bar(1),
() => foo("b"),
() => bar(2),
() => foo("c"),
() => bar(3)
];
functions.reduce((p, fn) => p.catch(fn), Promise.reject())
.then(value => {
console.log("Done", value);
});
function otherStuff(arg) {
return arg == 2;
}
function stuff(arg) {
return arg == "c";
}
function foo(arg) {
console.log("foo:", arg);
return new Promise(function(resolve, reject) {
if (stuff(arg)) {
console.log("foo:", arg, "resolving");
resolve('result from foo');
} else {
console.log("foo:", arg, "rejecting");
reject(); // <=== Note
}
});
}
// ... maybe more of these functions ...
function bar(arg) {
console.log("bar:", arg);
return new Promise(function(resolve, reject) {
if (otherStuff(arg)) {
console.log("bar:", arg, "resolving");
resolve('result from bar');
} else {
console.log("bar:", arg, "rejecting");
reject(); // <=== Note
}
});
}
let functions = [
() => foo("a"),
() => bar(1),
() => foo("b"),
() => bar(2),
() => foo("c"),
() => bar(3)
];
functions.reduce((p, fn) => p.catch(fn), Promise.reject())
.then(value => {
console.log("Done", value);
});
As you probably know, Array#reduce
is useful for "reducing" an array to a value, such as with a simple sum:
[1, 2, 3].reduce((sum, value) => sum + value, 0); // 6
In the above, for the "sum" equivalent, we start with a rejected promise and use catch
to create the chain of promises. The result of calling reduce
is the last promise from catch
.
But, if you want to use resolve(null)
instead, you use then
in a similar way:
foo("a")
.then(result => result ? result : bar(1))
.then(result => result ? result : foo("b"))
.then(result => result ? result : bar(2))
.then(result => result ? result : foo("d"))
.then(result => result ? result : bar(3))
.then(value => {
console.log("Done", value);
});
function otherStuff(arg) {
return arg == 2;
}
function stuff(arg) {
return arg == "c";
}
function foo(arg) {
console.log("foo:", arg);
return new Promise(function(resolve, reject) {
if (stuff(arg)) {
console.log("foo:", arg, "resolving");
resolve('result from foo');
} else {
console.log("foo:", arg, "resolving null");
resolve(null);
}
});
}
// ... maybe more of these functions ...
function bar(arg) {
console.log("bar:", arg);
return new Promise(function(resolve, reject) {
if (otherStuff(arg)) {
console.log("bar:", arg, "resolving");
resolve('result from bar');
} else {
console.log("bar:", arg, "resolving null");
resolve(null);
}
});
}
foo("a")
.then(result => result ? result : bar(1))
.then(result => result ? result : foo("b"))
.then(result => result ? result : bar(2))
.then(result => result ? result : foo("d"))
.then(result => result ? result : bar(3))
.then(value => {
console.log("Done", value);
});
Or with an array:
let functions = [
() => foo("a"),
() => bar(1),
() => foo("b"),
() => bar(2),
() => foo("c"),
() => bar(3)
];
functions.reduce((p, fn) => p.then(result => result ? result : fn()), Promise.resolve(null))
.then(value => {
console.log("Done", value);
});
function otherStuff(arg) {
return arg == 2;
}
function stuff(arg) {
return arg == "c";
}
function foo(arg) {
console.log("foo:", arg);
return new Promise(function(resolve, reject) {
if (stuff(arg)) {
console.log("foo:", arg, "resolving");
resolve('result from foo');
} else {
console.log("foo:", arg, "resolving null");
resolve(null);
}
});
}
// ... maybe more of these functions ...
function bar(arg) {
console.log("bar:", arg);
return new Promise(function(resolve, reject) {
if (otherStuff(arg)) {
console.log("bar:", arg, "resolving");
resolve('result from bar');
} else {
console.log("bar:", arg, "resolving null");
resolve(null);
}
});
}
let functions = [
() => foo("a"),
() => bar(1),
() => foo("b"),
() => bar(2),
() => foo("c"),
() => bar(3)
];
functions.reduce((p, fn) => p.then(result => result ? result : fn()), Promise.resolve(null))
.then(value => {
console.log("Done", value);
});
That works because if we get back a truthy value (or you could use result => result !== null ? result : nextCall()
), we return that result down the chain, which means that that then
returns a resolved promise with that value; but if we get back a falsy value, we call the next function and return its promise.
As you can see, this is a bit more verbose, which is part of why promises have this distinction between resolution and rejection.