【问题标题】:Nice way to do recursion with ES6 promises?用 ES6 承诺进行递归的好方法?
【发布时间】:2015-08-28 16:29:23
【问题描述】:

这是我得到的:

function nextAvailableFilename(path) {
    return new Promise(function (resolve, reject) {
        FileSystem.exists(path, function (exists) {
            if (!exists) return resolve(path);

            var ext = Path.extname(path);
            var pathWithoutExt = path.slice(0, -ext.length);

            var match = /\d+$/.exec(pathWithoutExt);
            var number = 1;

            if (match) {
                number = parseInt(match[0]);
                pathWithoutExt = pathWithoutExt.slice(0, -match[0].length);
            }

            ++number;

            nextAvailableFilename(pathWithoutExt + number + ext).then(function () {
                return resolve.apply(undefined, arguments);
            }, function () {
                return reject.apply(undefined, arguments);
            });
        });
    });
}

但我不喜欢最后的那个块 - 有没有办法用堆栈中的下一个“替换”当前承诺,而不是像我在这里所做的那样让一个承诺解决下一个承诺?

【问题讨论】:

  • 你可以创建一个包装对象,它只有一堆承诺,处理实际的解决/拒绝,然后继续下一个承诺
  • @MiltoxBeyond 我不确定我是否理解您要说的内容。我应该创建一个单独的对象来维护一个堆栈,并或多或少地解决/拒绝它们我现在做了什么?这会带来什么好处?
  • 它本质上是美观的,但你确实有更清晰的代码的好处,所以它应该很容易理解,如果你使用内存使用更轻的原型
  • 你为什么不只是链接承诺?
  • 没问题,只是提到它以防效率很重要。如果有很多冲突的文件,则通过执行fs.readdir() 并查看该数组以查找最佳文件名来尝试下一个尝试可能会更有效,因为您可以使用该文件调用跳过所有现有名称。

标签: javascript es6-promise


【解决方案1】:

这是一个使用承诺链和文件创建来避免竞争条件的版本。我使用了 bluebird Promise 库,因此我可以将 Promise 与 fs 库一起使用,以简化代码和错误处理:

var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var path = require('path');

// Creates next available xxx/yyy/foo4.txt numeric sequenced file that does
// not yet exist.  Returns the new filename in the promise
// Calling this function will create a new empty file.
function nextAvailableFilename(filename) {
    return fs.openAsync(filename, "wx+").then(function(fd) {
        return fs.closeAsync(fd).then(function() {
            return filename;
        });
    }, function(err) {
        if (err.code !== 'EEXIST') {
            // unexpected file system error
            // to avoid possible looping forever, we must bail 
            // and cause rejected promise to be returned
            throw err;
        }
        // Previous file exists so reate a new filename
        // xxx/yyy/foo4.txt becomes xxx/yyy/foo5.txt
        var ext = path.extname(filename);
        var filenameWithoutExt = filename.slice(0, -ext.length);
        var number = 0;
        var match = filenameWithoutExt.match(/\d+$/);
        if (match) {
            number = parseInt(match[0], 10);
            filenameWithoutExt = filenameWithoutExt.slice(0, -match[0].length);
        }
        ++number;
        // call this function again, returning the promise 
        // which will cause it to chain onto previous promise
        return nextAvailableFilename(filenameWithoutExt + number + ext);
    });
}

【讨论】:

  • wxwx+ 有什么区别?另外,fs.close 是异步的——我们应该等到文件完全关闭后再返回。
  • wx+ 只为读写而打开。在这种情况下可能与 wx.是的,它应该等到fs.close() 完成,因为调用者会想要使用该文件。我已经编辑过这样做。我还修复了一种类型以使用函数的承诺版本。
  • 酷。我现在明白你所说的“链接”是什么意思了。我以前读过这个,但我以前从未使用过这个功能。很方便。这对于 fs.open/close 的承诺版本肯定更好。
  • @Mark - 请参阅我最后关于无限循环的说明。当fs.openAsync() 失败时,它只需要在某些错误条件下继续尝试另一个名称。例如,如果在文件名(不存在的路径)或您没有创建权限的路径前面传递了虚假路径,这将永远循环。因此,基本上,它需要检查err 以查看错误是否是该文件已存在。由于 node.js 文档在这方面很糟糕,因此测试文件必须检查错误代码以查看要查找的错误代码。
  • @Mark,问题中的代码现在已经过测试并且可以工作。我添加了正确的错误代码检测以避免循环并修复了几个拼写错误。
【解决方案2】:

我也想出了一个不依赖于 bluebird.promisify 的解决方案。它应该处理文件创建失败的情况,而不是它已经存在的原因。

function createFile(path) {
    return new Promise(function (resolve, reject) {
        FileSystem.open(path, 'wx', function (err, fd) {
            if (err) return reject(err);
            FileSystem.close(fd, function (err) {
                if (err) return reject(err);
                return resolve();
            });
        });
    });
}

// todo: make more efficient by multiplying numbers by 2 or something like http://stackoverflow.com/a/1078898/65387

function nextFile(path) {
    return createFile(path).then(function () {
        return path;
    }, function (err) {
        if (err.code !== 'EEXIST') throw err; // error other than "file exists"

        var ext = Path.extname(path);
        var pathWithoutExt = path.slice(0, -ext.length);

        var match = /\d+$/.exec(pathWithoutExt);
        var number = 2;

        if (match) {
            number = parseInt(match[0]) + 1;
            pathWithoutExt = pathWithoutExt.slice(0, -match[0].length);
        }

        return nextFile(pathWithoutExt + number + ext);
    });
}

【讨论】:

  • 仅供参考,一旦你开始手动承诺一些 fs 调用并真正学习如何使用承诺,你很快就会发现你只是希望整个 fs api 被承诺。更进一步,一旦你接触到 Promise,你会很快发现 Bluebird(或其他 Promise 库)提供的额外功能非常非常有用。我可以理解,如果你想为重新分发做一些小而轻的东西,我可以理解避免第三方,但对于你自己的项目,一个承诺的 fs 模块是黄金。
  • 您的错误处理无法正常工作。检查err.errno 时,您需要throw err,而不是return errreturn err 将导致 promise 以 err 作为返回值被解析,而不是被拒绝。如果你在 .then() 处理程序中抛出,那么它将导致 promise 被拒绝。如果你返回的不是 Promise,那将是 resolve 的值。
  • 仅供参考,当我在 Windows 上运行此代码时,当文件已存在时,我看到 err.errno === -4075。也许err.code 更跨平台?
  • 好点。谢谢您的帮助。我不太确定我需要扔还是返回;我认为返回将保持当前的解决/拒绝状态。我将err.errno 切换为err.code
  • 是的,通过 promises 学习是件好事。如果您有一个.then() 错误处理函数,那么该承诺假定您已经“处理”了该错误,因此如果您只是从该处理程序返回,那么生成的承诺将被解决,而不是被拒绝。要拥有一个处理程序并使生成的 Promise 被拒绝,您可以返回一个被拒绝的 Promise 或抛出一个错误(它将捕获并变成一个被拒绝的 Promise)。
猜你喜欢
  • 2016-04-02
  • 1970-01-01
  • 2021-11-21
  • 2014-02-04
  • 1970-01-01
  • 2017-05-08
  • 1970-01-01
  • 2018-11-07
  • 2018-05-29
相关资源
最近更新 更多