【问题标题】:stop promise chain with multiple catches停止具有多个捕获的承诺链
【发布时间】:2017-03-03 16:57:39
【问题描述】:

在 Node.js 中,我需要读取一个文件并验证它的内容,所有这些都是异步的。我正在使用 Node.js 6.6bluebird 3.4.6

示例代码:

// pseudo function to read file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function readFile(flag) {
    return new Promise(function (resolve, reject) {
        console.log('Reading file...');
        if (flag) {
            resolve('File contents');
        } else {
            reject('readFile error');
        }
    });
}

// pseudo function to validate file contents - resolves when 'flag' is true, rejects when 'flag' is false.
function validate(fileContents, flag) {
    return new Promise(function (resolve, reject) {
        console.log('Validating file: ', fileContents);
        if (flag) {
            resolve('Validate passed');
        } else {
            reject('validation failed');
        }
    });
}


readFile(false)
    .then(function (fileContents) {
        console.log('Successfully read the file:', fileContents);
        return fileContents;
    })
    .catch(function (fileReadErr) {
        console.log('Failed to read the file:', fileReadErr);
        throw fileReadErr; // or, return Promise.reject(err);
    })
    .then(function (fileContents) {
        return validate(fileContents, false);
    })
    .then(function (result) {
        console.log('Successfully validated the file:', result);
    })
    .catch(function (err) {
        console.log('Failed to validate the file:', err);
    })
    ;
<script src="https://cdn.jsdelivr.net/bluebird/3.4.6/bluebird.min.js"></script>

上面的代码会打印出来

Reading file...
Failed to read the file: readFile error
Failed to validate the file: readFile error

上面的promise链大致翻译成下面的同步代码:

try {
    let fileContents;

    try {
        fileContents = readFile(false);
        console.log('Successfully read the file:', fileContents);
    } catch (e) {
        console.log('Failed to read the file:', e);
        throw e;
    }

    let validationResult = validate(fileContents, false);
    console.log('Successfully validated the file:', validationResult);
} catch (err) {
    console.log('Failed to validate the file:', err);
}

并且,在第一个 catch 方法中抛出或拒绝 仍将调用第二个 catch 方法

我的问题:一旦文件读取失败,有什么办法可以断链吗?我的目标是从 express.js 路由返回不同的 HTTP 状态代码(文件读取错误:500,验证失败:400)。

我知道使用非标准专用 catch 方法的解决方案,但这需要特殊处理。从某种意义上说,我需要在错误对象中抛出错误或需要一些过滤键,而这两者都不在我手中,并且需要一些工作来实现它。此解决方案在 bluebird docs 和此处提到:Handling multiple catches in promise chain

【问题讨论】:

  • 对于那些想要将其标记为 stackoverflow.com/questions/26076511/… 的副本的人,我想知道任何其他可能的解决方案,同样我不想在一个 catch 中处理所有错误@Esailija(蓝鸟作者)在这里提到的链的末端stackoverflow.com/a/26077569/340290
  • 鉴于您的问题的限制,答案可能是“不,没有办法打破捕获链”。在链的末端使用 final catch 处理程序有什么反对意见?
  • 要在最后使用catch,至少我需要维护一个标志或者必须验证错误对象才能知道不同的错误状态。它会变成 if-elseif-else 类型。不是吗?
  • 正确。另一种选择是使用 Babel 和 async/await 或像 co 这样的异步生成器库
  • 是的,这就是我目前正在查看的内容。我或多或少需要这个功能:caolan.github.io/async/docs.html#series

标签: node.js promise bluebird


【解决方案1】:

到目前为止,最简单的解决方案是使用我所说的“绝缘卡扣”。即,每个.catch() 都是专家的模式,与整个过程中的特定步骤相关联,并且主链仅包含 .thens(最终是单个终端捕获)。

此外,在这种情况下,通过重新抛出带有附加属性的 Error 对象,沿错误路径传递附加信息也很有用。这避免了对自定义错误的需要。

Promise.resolve()
.then(function() {
    return readFile(false)
    .then(function (fileContents) {
        console.log('Successfully read the file:', fileContents);
        return fileContents;
    })
    .catch(function (error) {
        error.code = 521; // or whatever
        error.customMessage = 'Failed to read the file';
        throw error;
    })
})
.then(function (fileContents) {
    return validate(fileContents, false)
    .then(function (result) {
        console.log('Successfully validated the file:', result);
        return fileContents;
    })
    .catch(function (error) {
        error.code = 522; // or whatever
        error.customMessage = 'Failed to validate the file';
        throw error;
    });
})
.catch(function(error) { // terminal catch.
    console.log(error); 
    // It's possible for unaugmented errors to reach this point,
    // so be sure to test for the extra properties before trying to use them.
    if(error.code) {...}
    if(error.customMessage) {...}
    // Note also that the original error.message is still intact.
});

最初的Promise.resolve() 不是绝对必要的,但有助于保持其他一切对称。

这适用于任何 Promises/A+ 库。不需要蓝鸟糖。

【讨论】:

    【解决方案2】:

    您可以像这样创建自定义错误类型:

    ReadFileError = function() {};
    ReadFileError.prototype = Error.prototype;
    
    ValidationError = function() {};
    ValidationError.prototype = Error.prototype;
    

    然后,您可以从 Promise 中throw 而不是拒绝:

    function validate(fileContents, flag) {
        return new Promise(function (resolve, reject) {
            console.log('Validating file: ', fileContents);
            if (flag) {
                resolve('Validate passed');
            } else {
                throw new ReadFileError('readFile error');
            }
        });
    }
    

    然后你可以根据它们的类型捕获不同的错误:

    readFile(false)
        .then(function (fileContents) {
            console.log('Successfully read the file:', fileContents);
            return fileContents;
        })
        .then(function (fileContents) {
            return validate(fileContents, false);
        })
        .then(function (result) {
            console.log('Successfully validated the file:', result);
        })
        .catch(ReadFileError, function (err) {
            console.log(..., err);
        })
        .catch(ValidationError, function (err) {
            console.log(..., err);
        })
        catch(function(err) {
            ...
        });
    

    【讨论】:

    • 感谢您的回答。但我知道这个解决方案,正如我提到的,抛出不同的异常不在手边。我正在使用bluebird.promisifyAll 方法用 Promise 包装 fs, mysql` 方法。这些方法将根据回调调用简单地解析或拒绝。
    【解决方案3】:

    也许更多的人会遇到同样的问题。我个人认为这不是最好的方法,因为你的应用程序会抛出伪错误,这可能会被服务器上的其他错误处理错误地处理。但它就像你建议的那样工作:

        // pseudo function to read file contents - resolves when 'flag' is true, rejects when 'flag' is false.
    function readFile(flag) {
        return new Promise(function (resolve, reject) {
            console.log('Reading file...');
            if (flag) {
                resolve('File contents');
            } else {
                throw new Error ('errorReading');
            }
        });
    }
    
    // pseudo function to validate file contents - resolves when 'flag' is true, rejects when 'flag' is false.
    function validate(fileContents, flag) {
        return new Promise(function (resolve, reject) {
            console.log('Validating file: ', fileContents);
            if (flag) {
                resolve('Validate passed');
            } else {
                throw new Error ('validationFailed');
    
            }
        });
    }
    
    readFile(false)
        .then(function (fileContents) {
            console.log('Successfully read the file:', fileContents);
            return fileContents;
        })    
        .then(function (fileContents) {
            return validate(fileContents, false);
        })
        .then(function (result) {
            console.log('Successfully validated the file:', result);
        })
        .catch((error) => {
            console.log(error.name);
            console.log(error.message);
            if (error.message === 'errorReading'){
                console.log('error 500 - File could\'d be read');
                // Maybe more custom error processing here
                //return res.status(500).send(JSON.stringify({
                //           'status' : 'error',
                //            'message' : 'File could\'d be read'
                //}));
            } else  if (error.message=== 'validationFailed'){
                console.log('error 500 - Validation not OK');
                // Maybe more custom error processing here            
                //return res.status(500).send(JSON.stringify({
                //            'status' : 'error',
                //            'message' : 'Validation not OK'
                //}));
            } else {
                console.log('error 500 - Some really bad stuff!');
                //return res.status(500).send(JSON.stringify({
                //            'status' : 'error',
                //            'message' : 'Some really bad stuff!',
                //            'errorMessage': error
                //}));
            }
        });
    <script src="https://cdn.jsdelivr.net/bluebird/3.4.6/bluebird.min.js"></script>

    请注意,我注释掉了 express 的res.send,以避免在处理这个sn-p时出错!

    【讨论】:

      【解决方案4】:

      据我了解您想要实现的目标,我建议始终使用单个 catch 块(何时可以避免在承诺逻辑中引入嵌套,这在少数用例中完全可以,但应尽可能避免,因为你可能会以缩进的方式结束承诺地狱)

      您能否以统一的方式处理函数readFilevalidate 中的所有错误,例如:

      const error = new Error('something bad happened')
      error.status = 500
      return reject(error)
      

      那么你可以在一个基于status的catch块中处理错误逻辑,例如res.status(err.status || 500).json(...)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-11-22
        • 2021-11-06
        • 1970-01-01
        • 2014-12-29
        • 1970-01-01
        • 2017-02-18
        相关资源
        最近更新 更多