Given a function, fn
, which returns a promise, and an arbitrary length array of data (e.g. data = ['apple', 'orange', 'banana', ...]
) how do you chain function calls on each element of the array in sequence, such that if fn(data[i])
resolves, the whole chain pletes and stops calling fn
, but if fn(data[i])
rejects, the next call fn(data[i + 1])
executes?
Here is a code example:
// this could be any function which takes input and returns a promise
// one example might be fetch()
const fn = datum =>
new Promise((resolve, reject) => {
console.log(`trying ${datum}`);
if (Math.random() < 0.25) {
resolve(datum);
} else {
reject();
}
});
const foundResult = result => {
// result here should be the first value that resolved from fn(), and it
// should only be called until the first resolve()
console.log(`result = ${result}`);
};
// this data can be purely arbitrary length
const data = ['apple', 'orange', 'banana', 'pineapple', 'pear', 'plum'];
// this is the behavior I'd like to model, only for dynamic data
fn('apple').then(foundResult)
.catch(() => {
fn('orange').then(foundResult)
.catch(() => {
fn('banana').then(foundResult)
.catch(() => {
/* ... and so on, and so on ... */
});
});
});
I feel like maybe there's an elegant solution to this pattern that I'm missing. The behavior is very similar to Array.some()
, but I've e up empty trying to fiddle with that.
EDIT: I switched from numeric data to string to stress that the solution needs to not be reliant on the data being numeric.
EDIT #2: Just to clarify further, fn
could be any function that accepts input and returns a promise. The fn
implementation above was just to give a plete example. In reality, fn
could actually be something like an API request, a database query, etc.
Given a function, fn
, which returns a promise, and an arbitrary length array of data (e.g. data = ['apple', 'orange', 'banana', ...]
) how do you chain function calls on each element of the array in sequence, such that if fn(data[i])
resolves, the whole chain pletes and stops calling fn
, but if fn(data[i])
rejects, the next call fn(data[i + 1])
executes?
Here is a code example:
// this could be any function which takes input and returns a promise
// one example might be fetch()
const fn = datum =>
new Promise((resolve, reject) => {
console.log(`trying ${datum}`);
if (Math.random() < 0.25) {
resolve(datum);
} else {
reject();
}
});
const foundResult = result => {
// result here should be the first value that resolved from fn(), and it
// should only be called until the first resolve()
console.log(`result = ${result}`);
};
// this data can be purely arbitrary length
const data = ['apple', 'orange', 'banana', 'pineapple', 'pear', 'plum'];
// this is the behavior I'd like to model, only for dynamic data
fn('apple').then(foundResult)
.catch(() => {
fn('orange').then(foundResult)
.catch(() => {
fn('banana').then(foundResult)
.catch(() => {
/* ... and so on, and so on ... */
});
});
});
I feel like maybe there's an elegant solution to this pattern that I'm missing. The behavior is very similar to Array.some()
, but I've e up empty trying to fiddle with that.
EDIT: I switched from numeric data to string to stress that the solution needs to not be reliant on the data being numeric.
EDIT #2: Just to clarify further, fn
could be any function that accepts input and returns a promise. The fn
implementation above was just to give a plete example. In reality, fn
could actually be something like an API request, a database query, etc.
-
But
fn
always returns something on resolved, right? – emil Commented Sep 2, 2017 at 1:18 -
@emil Not necessarily, no. In the case where a promise resolves with no data, then
result
insidefoundResult()
will simply beundefined
, which is fine. – FtDRbwLXw6 Commented Sep 2, 2017 at 1:29
5 Answers
Reset to default 5You could use async/await
and a loop:
async function search() {
for (let item of data) {
try {
return await fn(item);
} catch (err) { }
}
throw Error ("not found");
}
search().then(foundResult).catch(console.log);
fn
can return either Promise (awaited) or simply a value (returned)- your
data
could be an infiniteiterable
sequence (generator) - in my opinion, its also easy to read and understand intent.
here is the output if the sequence fails:
trying apple
trying orange
trying banana
trying pineapple
trying pear
trying plum
Error: not found
support for async is native in es2017, but can be transpiled to es3/es5 with babel or typescript
You can use Array.reduce to get the desired data.
data.reduce((promise, item) => promise.then(
(param) => {
if (param) return Promise.resolve(param);
return fn(item).catch(() => Promise.resolve());
}
), Promise.resolve())
.then(foundResult)
Basically it will pass over the result to the end once passes. And if fn
is failed, it will pass over undefined valued promise to next chain to trigger fn
.
Write a search function like below:
function search(number) {
if (number < data.length) {
fn(data[number]).then(foundResult)
.catch(() => search(number + 1));
}
}
search(0);
You could write a very simple recursive function that will stop on the first resolve and recurse on the catch.
function find_it(arr) {
let [head, ...rest] = arr
if (!head) return console.log("not found") // all rejects or no data
fn(head)
.then(r => foundResult(r) )
.catch(r => find_it(rest))
}
find_it(data)
This has the benefit of stopping on the first match without calling all the values if a match is found and not caring about the length of data
unless you exceed the stack size in the recursion. Of course it would be easy to modify the action on the edge case when all promises reject to do something.
Result when found:
$ node ./test
trying apple
trying orange
result = orange
and not found:
$ node ./test trying apple
trying orange
trying banana
trying pineapple
trying pear
trying plum
not found
What you are trying to do could be done like this I guess (it is not strictly equivalent, see reserves below):
const fn = n => new Promise(() => {});
const seq = [];
const process = n => fn(n).then(foundResult);
seq.slice(1).reduce((operation, item) => {
return operation
.catch(() => process(item));
}, process(seq[0]));
With Promise.race :
const fetchAll = ( urls = [] ) => Promise.race(urls.map(fn)).then(foundResult);
However I am not sure this is what you are trying to achieve : for instance in your snippet you are not returning anything in your catch
handler which means that the foundResult
is most likely a side effect. Moreover when reading your code snippet you are catching errors that can be raised from inside the foundResult
and not from the fn
function.
As a rule of thumb I try to always have my promises fulfilled by a known "type" or rejected with an error. It's not clear in your example that the promise you are generating will be settled with any value, whether it's a rejectionValue
or a fulfillmentValue
.
Perhaps if you would provide a use case I could help you a bit.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744349930a4569913.html
评论列表(0条)