Return when first promise resolves

為{幸葍}努か 提交于 2021-02-08 11:33:56

问题


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:

  1. Calling resolve(...) doesn't actually exit the loop, so the program opens every file in the list, even when doesn't need to.
  2. 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.
  3. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!