简体   繁体   English

第一个承诺解决后返回

[英]Return when first promise resolves

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() . 它们是配置文件,因此顺序是确定性很重要,因此我不能使用.race() The version I have below maps over each file in order, tries to load it, and if it loads successfully, calls resolve. 我下面的版本按顺序映射每个文件,尝试加载它,如果加载成功,则调用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. 调用resolve(...)实际上并不会退出循环,因此该程序即使在不需要时也会打开列表中的每个文件。
  2. The rejection condition (at this is required to reject when we don't receive any files ) seems like a hack. 拒绝条件( this is required to reject when we don't receive any files拒绝的条件),看起来像是黑客。 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. 我可能只用一个Promise.filter调用就可以做到这Promise.filter ,但是如果不需要的话,我不想查询每个文件。

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

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 (eg via _.find ). 如果要尝试并行读取所有内容并选择列表中的第一个成功对象,则可以使用Promise.map + Promise.map .reflect()然后仅过滤结果(例如,通过_.find )。

This can be achieved by recursion but also by building a catch chain using Array#reduce(): 这可以通过递归来实现,也可以通过使用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);
});

The catch chain ensures that every time fs.readFileAsync(path, 'utf-8') fails, the next path is tried. 捕获链确保每次fs.readFileAsync(path, 'utf-8')失败时,都尝试下一个路径。

The first successful fs.readFileAsync(path, 'utf-8') will drop through to .then(function([filename, configFile]) {...} . 第一个成功的fs.readFileAsync(path, 'utf-8')将进入.then(function([filename, configFile]) {...}

Total failure will drop through to .catch(function(err) {...} . 完全失败将落入.catch(function(err) {...}

There is this hackish approach to solve this problem neatly. 有这种骇人听闻的方法可以巧妙地解决这个问题。 You may invert the promises like; 您可以像这样invert承诺。

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. 实际上,当与Promise.all()一起使用时, Promise.all()可以通过忽略被拒绝的对象来获得第一个解决承诺,因此非常方便。 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() . 我的意思是倒立时,拒绝了所有的承诺(解决)可能会被忽视,而第一解析(拒绝)一个被在抓.catch()阶段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}`)); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM