第一个承诺解决时返回

时间:2016-12-23 19:39:14

标签: javascript promise bluebird

目标

我在数组中有一堆文件名,并且想要读取存在的第一个文件的内容。他们配置文件,因此订单具有确定性非常重要,因此我无法使用.race()。我下面的版本按顺序映射每个文件,尝试加载它,如果加载成功,则调用解析。

问题

以下是此实施的几个问题:

  1. 调用resolve(...)实际上并不退出循环,因此程序会打开列表中的每个文件,即使不需要也是如此。
  2. 拒绝条件(this is required to reject when we don't receive any files)似乎是一种黑客行为。但是,如果它不在这里,承诺永远不会被拒绝。
  3. 分辨率代码看起来像一个承诺反模式。
  4. 有没有更好的方法来构建这个?我可以通过一次Promise.filter电话来完成,但如果我不需要,我也不想查询每个文件。

    由于

    代码

    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)
    })
    

3 个答案:

答案 0 :(得分:1)

如果您想要顺序迭代,只需使用递归方法:

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)
})

如果您想尝试并行读取它们并在列表中选择第一个成功,您可以使用Promise.map + .reflect()然后只过滤结果(例如通过_.find)。

答案 1 :(得分:1)

这可以通过递归实现,也可以通过使用Array#reduce()构建catch链来实现:

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

捕获链确保每次fs.readFileAsync(path, 'utf-8')失败时,都会尝试下一个路径。

第一个成功的fs.readFileAsync(path, 'utf-8')将逐渐转移到.then(function([filename, configFile]) {...}

总失败将降至.catch(function(err) {...}

答案 2 :(得分:0)

有这种hackish方法可以整齐地解决这个问题。您可以invert承诺,例如;

var invert = pr => pr.then(v => Promise.reject(v), x => Promise.resolve(x));
实际上,当与Promise.all()一起使用时,通过忽略被拒绝的承诺来获得第一个解决承诺,这实际上很方便。我的意思是倒置时,所有拒绝(已解决)的承诺可能会被忽视,而第一个解决(拒绝)的承诺会被.catch() Promise.all()阶段捕获。酷..!

观看此内容;



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}`));