【问题标题】:Promise default catch implementation with override承诺默认捕获实现与覆盖
【发布时间】:2021-09-10 15:46:33
【问题描述】:

我有一个包含多种服务和方法的 API。其中一些调用我想手动处理,例如,如果错误是预期的,则向用户显示有用的错误消息。

对于其余的调用,我想要一个“catch”的默认实现,它会发出一条消息,一些全局侦听器将处理并显示更通用的错误消息。

我发现另一个堆栈溢出帖子几乎给了我想要的东西: Promises and generic .catch() statements

即默认catch但重新抛出错误。问题是,如果我为某些特定服务实现自己的 catch,我不希望通用 catch 启动,因为这将显示全局通用错误。

有没有办法让 catch 的默认实现在手动实现时被覆盖?

示例:

let promise = pageService.getPage({pageId})
.then( (resp) => {} )
// on error, use the default catch

let promise = pageService.getPage({pageId})
.then( (resp) => {} )
.catch( (error) => { /* I want to handle the error, override the default error implementation */} )

【问题讨论】:

  • 假设这段代码与使用 try/catch 完成的异常处理完全同步——你会怎么写?

标签: javascript promise es6-promise


【解决方案1】:

据我所知,promise 在“先到先得”的基础上工作,这意味着第一个注册其 catch 函数的人也将是第一个被调用的人。

到目前为止,我能想到的唯一丑陋的解决方法是扩展正在处理的错误,以便我可以识别它是否已经被处理。如果我们以您之前的示例为例:

const pageService = {
    getPage: (pageId) => {
        return doStuff(). catch((error) => {
            error.handled = false;
            setTimeout(() => {
                if(!error.handled) {
                    // do your default handling
                }
            )}, 1);
        });
        throw error; //Let possible other handlers first have their go 
    }
}

let promise = pageService.getPage({pageId})
.then( (resp) => {} )
// on error, use the default catch

let promise = pageService.getPage({pageId})
.then( (resp) => {} )
.catch( (error) => {
    //handle the error here
    error.handled = true;
 })

【讨论】:

    【解决方案2】:

    我找到了一个使用 js 装饰器的优雅解决方案。 JS 装饰器仍然是stage two proposal。要使用它们,请添加

    "ecmaFeatures": {
        "legacyDecorators": true
      }
    

    到你的 eslint 配置。

    然后你可以像这样使用装饰器:

    实现装饰器:

    function error_handler(target, property, descriptor){
        let default_function = descriptor.value //The original function  definition
        
        descriptor.value = function () {
            // The modified function
            let result = default_function.call(this, ...arguments) // Call original function with object context and arguments
            let do_default_error = true // Do default error handling ?
            if (arguments.length > default_function.length){
                // If the modified function is called with more arguments than the default function 
                // use the last argument to decide if we should do default error handling or not
                do_default_error = Boolean(arguments[arguments.length])
            }
            if (do_default_error) {
                return result.catch((error) => {
                    // The default error handling 
                    error_notification(error)
                    throw error // Ensure promise chain 
                })
            }else{
                return result // Do nothing
            }
        }
        return descriptor // return the modified function
    }
    

    在您的 api 定义中使用装饰器:

    const pageService = {
        @error_handler
        getPage(pageId){
            return doStuff(pageId) // doStuff must return a Promise 
        },
        @error_handler
        postPage(payload) {
            return otherStuff()
        }
    }
    

    利润

    带有默认错误处理程序的简单 api 调用如下所示:

    pageService.getPage(id).then((ok_result) => {foo()})
    

    您仍然可以添加其他捕获 / finally 并且它们的行为符合预期

    pageService.getPage(id).then((ok_result) => {foo()}).catch((error) => {
        extraSteps()
    }).finally(() => {cleanup()})
    
    

    要禁用默认错误处理程序,请传递 false

    pageService.getPage(id, false).then((ok_result) => {foo()}).catch((error) => {
        doSpecialTreatement()
    })
    
    

    这种方法是我见过的最干净的方法。定义新的 api 函数很简单,用法也很简单。有一些缺点:

    使用第二阶段提案。据我了解,babel 会处理这个问题。

    您的代码中发生了一些神奇的事情。其他开发人员可能会感到困惑,为什么要传递一个额外的参数。您可以调整错误处理程序以始终使用最后一个参数并像这样定义您的 api 函数:

    const pageService = {
        @error_handler
        getPage(pageId, doErrorHandling){
            return doStuff(pageId)
        }
    

    这有点清楚,但您必须禁用 eslint 没有未使用的变量检查此文件。另一个想法是始终将对象传递给您的 api 函数并查找 doErrorHandling 属性。

    免责声明:我不会认为自己是 js 专家。如果您喜欢这种方法,请自行判断。如果您发现任何严重的缺点,请发表评论。

    感谢阅读:-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-19
      • 1970-01-01
      • 2020-03-17
      • 1970-01-01
      • 2012-12-05
      • 1970-01-01
      • 1970-01-01
      • 2021-11-06
      相关资源
      最近更新 更多