问题
Goal
I have a bunch of file names in an array, and would like to read the contents of the first of the files that exists. They're config files, so it's important that the order is deterministic, so I can't use .race()
. The version I have below maps over each file in order, tries to load it, and if it loads successfully, calls resolve.
Problems
Here are a couple of issues with this implementation:
- Calling
resolve(...)
doesn't actually exit the loop, so the program opens every file in the list, even when doesn't need to. - The rejection condition (at
this is required to reject when we don't receive any files
) seems like a hack. However, if it's not here, the promise is never rejected. - The resolution code seems suspiciously like a promise anti-pattern.
Are there any better ways to do structure this? I could probably do it with a single Promise.filter
call, but I don't want to query every file if I don't need to.
Thanks
Code
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var _ = require('lodash');
new Promise((resolve, reject) => {
// Resolve with the first of the files below that exists
return Promise.mapSeries(
['./file_that_doesntexist.json', '../file_that_might_exist.json', './file_that_doesnt_exist_either.json', '../file_that_exists.json']
, (filename) => fs.readFileAsync(filename, 'utf-8')
.then(file => {
resolve([filename, file]);
return true;
})
.catch(_.stubFalse)
)
.then(files => { // this is required to reject when we don't receive any files
if(!files.some(x => x))
reject('did not receive any files');
});
})
.then(function([filename, configFile]) {
// do something with filename and configFile
})
.catch(function(err) {
console.error(err)
})
回答1:
This can be achieved by recursion but also by building a catch
chain using Array#reduce():
var paths = ['./file_that_doesntexist.json', '../file_that_might_exist.json', './file_that_doesnt_exist_either.json', '../file_that_exists.json'];
// Resolve with the first of the files below that exists
paths.reduce(function(promise, path) {
return promise.catch(function(error) {
return fs.readFileAsync(path, 'utf-8').then(file => [path, file]);
});
}, Promise.reject())
.then(function([filename, configFile]) {
// do something with filename and configFile
})
.catch(function(err) {
console.error('did not receive any files', err);
});
The catch chain ensures that every time fs.readFileAsync(path, 'utf-8')
fails, the next path is tried.
The first successful fs.readFileAsync(path, 'utf-8')
will drop through to .then(function([filename, configFile]) {...}
.
Total failure will drop through to .catch(function(err) {...}
.
回答2:
If you want sequential iteration, just use a recursive approach:
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
function readFirstOf(filenames)
if (!filenames.length)
return Promise.reject(new Error('did not receive any files'));
return fs.readFileAsync(filenames[0], 'utf-8')
.then(file =>
[filenames[0], file]
, err =>
readFirstOf(filenames.slice(1))
);
}
readFirstOf(['./file_that_doesntexist.json', '../file_that_might_exist.json', './file_that_doesnt_exist_either.json', '../file_that_exists.json'])
.then(function([filename, configFile]) {
// do something with filename and configFile
})
.catch(function(err) {
console.error(err)
})
If you want to try to read them all in parallel and the select the first successful in the list, you can use Promise.map + .reflect() and then just filter the results (e.g. via _.find
).
回答3:
There is this hackish approach to solve this problem neatly. You may invert
the promises like;
var invert = pr => pr.then(v => Promise.reject(v), x => Promise.resolve(x));
which in fact comes handy when used with Promise.all()
to get the first resolving promise by ignoring the rejected ones. I mean when inverted, all promises rejected (resolved) may go unnoticed while the first resolving (rejecting) one gets caught at the .catch()
stage of Promise.all()
. Cool..!
Watch this;
var invert = pr => pr.then(v => Promise.reject(v), x => Promise.resolve(x)),
promises = [Promise.reject("No such file"),
Promise.reject("No such file either"),
Promise.resolve("This is the first existing files content"),
Promise.reject("Yet another missing file"),
Promise.resolve("Another file content here..!")];
Promise.all(promises.map(pr => invert(pr)))
.catch(v => console.log(`First successfully resolving promise is: ${v}`));
来源:https://stackoverflow.com/questions/41307031/return-when-first-promise-resolves