【问题标题】:Return when first promise resolves当第一个承诺解决时返回
【发布时间】:2017-05-09 11:44:26
【问题描述】:

目标

我在一个数组中有一堆文件名,并且想读取第一个存在的文件的内容。它们是配置文件,所以顺序是确定性的很重要,所以我不能使用.race()。我在下面的版本按顺序映射每个文件,尝试加载它,如果加载成功,调用resolve。

问题

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

  1. 调用resolve(...) 并不会真正退出循环,因此程序会打开列表中的每个文件,即使在不需要时也是如此。
  2. 拒绝条件(this is required to reject when we don't receive any files)似乎是一个 hack。但是,如果它不在这里,则承诺永远不会被拒绝。
  3. 解析代码看起来有点像 promise 反模式。

有没有更好的方法来构建这个?我可能可以通过一个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)
})

【问题讨论】:

  • 您可以尝试在 Promise 中使用 for...in 循环,然后在成功找到第一个文件时跳出循环。

标签: javascript promise bluebird


【解决方案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)。

【讨论】:

    【解决方案2】:

    这可以通过递归来实现,也可以通过使用 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);
    });
    

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

    第一个成功的fs.readFileAsync(path, 'utf-8') 将直通.then(function([filename, configFile]) {...}

    完全失败将传递到.catch(function(err) {...}

    【讨论】:

      【解决方案3】:

      有一种 hackish 方法可以巧妙地解决这个问题。你可以invert这样的承诺;

      var invert = pr => pr.then(v => Promise.reject(v), x => Promise.resolve(x));
      

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

      看这个;

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

      【讨论】:

      • 聚合后重新反相会产生将成功和失败放在正确路径上的效果,即invert(Promise.all(promises.map(invert)))
      猜你喜欢
      • 2016-07-20
      • 1970-01-01
      • 2022-01-18
      • 2021-03-17
      • 2016-08-19
      • 1970-01-01
      • 2015-11-27
      • 1970-01-01
      • 2021-05-25
      相关资源
      最近更新 更多