代码之家  ›  专栏  ›  技术社区  ›  Flame_Phoenix

如何取消这些承诺?

  •  2
  • Flame_Phoenix  · 技术社区  · 6 年前

    背景

    我有一个向服务器发出请求的函数。如果请求失败,我希望: 1。记录错误 2。运行终端命令 2.1记录命令是否失败或成功

    为了实现这一点,我有以下代码:

    const createRequest = ( { request, logger, terminal } ) => ( { endpoint, timeout } ) =>
        request.get( endpoint, { timeout } )
            .then( response =>
                logger.info( { event: "Heartbeat request succeeded.", status: response.status } )
            )
            .catch( err =>
                logger.error( { event: "Heartbeat request failed.", err } )
                    .then( ( ) => terminal.runAsync( "pm2 restart myAPI" ) )
                    .then( ( ) => logger.info( { event: "Restarted API." } ) )
                    .catch( err => logger.error( { event: "Failed to restart API.",  err } ) )
            );
    

    现在,有几点需要注意: -日志记录是异步的(将信息发送到远程服务器) -运行终端命令是异步的 -发出请求显然是异步的

    有问题吗?

    我的问题是我的捕获物里面有一个承诺,这意味着我有巢穴。 现在,我强烈反对嵌套的承诺,所以我真的想摆脱它,但我不知道怎么做。

    问题

    1. 有没有可能在一个 catch ?
    2. 如果有,怎么办?
    2 回复  |  直到 6 年前
        1
  •  3
  •   Mulan    6 年前

    有问题吗?

    我的问题是我的捕获物里面有一个承诺,这意味着我有巢穴。现在,我强烈反对嵌套的承诺,所以我真的想摆脱它,但我不知道怎么做。

    火焰凤凰

    问题是你认为你有问题,或者你把这个问题贴在stackoverflow上,而不是 CodeReview . 这个 article you linked 显示在何处对嵌套的承诺采用幼稚的观点

    你会得到一大堆相互嵌套的承诺:

    loadSomething().then(function(something) {
        loadAnotherthing().then(function(another) {
                        DoSomethingOnThem(something, another);
        });
    });
    

    之所以这样做,是因为您需要对这两个承诺的结果进行处理,因此您可以将它们串起来,因为then()只传递上一个返回的结果。

    你这么做的真正原因是你不知道 Promise.all() 方法。

    _“代码猴, http://taoofcode.net

    不, Promise.all 只能 有时 替换嵌套的承诺。一个简单的反例是,一个承诺的价值取决于另一个承诺,因此这两个承诺 必须 按顺序排列

    getAuthorByUsername (username) .then (a => getArticlesByAuthorId (a.id))
    

    嵌套承诺不是 总是 有必要,但我认为这是一种“反模式”,鼓励人们在意识到这种差异是有害的之前避免它。


    语句不起作用

    这个 other linked article 显示你可能再次被误导的地方

    别误会我,异步/等待不是世界上所有邪恶的源头。我用了几个月才学会喜欢它。因此,如果您觉得编写命令式代码很方便,那么学习如何使用Async/Wait来管理异步操作可能是一个不错的选择。

    但是,如果您喜欢承诺,并且您喜欢学习并将越来越多的功能性编程原则应用到代码中,那么您可能希望完全跳过异步/等待代码,停止思考命令式编程,转而使用这个新的旧范式。

    加布里埃尔·蒙特斯

    只是这没有任何意义。如果您在javascript中查看所有必需的关键字,您将不会注意到它们中的任何一个值。为了说明我的意思,考虑

    let total = if (taxIncluded) { total } else { total + (total * tax) }
    // SyntaxError: expected expression, got keyword 'if'
    

    或者如果我们尝试使用 if 在另一个表达式的中间

    makeUser (if (person.name.length === 0) { "anonymous" } else { person.name })
    // SyntaxError: expected expression, got keyword 'if'
    

    那是因为 如果 是一个 陈述 而且它从不评估为一个“值”,相反,它只能依靠副作用。

    if (person.name.length === 0)
      makeUser ("anonymous") // <-- side effect
    else
      makeUser (person.name) // <-- side effect
    

    下面 for 从不计算为值。相反,它依靠副作用来计算 sum

    let sum = 0
    let numbers = [ 1, 2, 3 ]
    for (let n of numbers)
      sum = sum + n        // <-- side effect
    console.log (sum)      // 6
    

    同样的道理也适用于 do , while , switch 甚至 return 所有其他必要的关键词都是 声明 因此,需要依靠副作用来计算值。

    那么,什么计算值呢? 表达 评估为值

    1                          // => 1
    5 + 5                      // => 10
    person.name                // => "bobby"
    person.name + person.name  // => "bobbybobby"
    toUpper (person.name)      // => "BOBBY"
    people .map (p => p.name)  // => [ "bobby", "alice" ]
    

    async await 不是陈述

    可以将异步函数赋给变量

    const f = async x => ...
    

    或者可以将异步函数作为参数传递

    someFunc (async x => ... )
    

    即使是一个 异步的 函数不返回任何值, 异步的 仍然保证我们会得到承诺的价值

    const f = async () => {}
    f () .then (() => console.log ("done"))
    // "done"
    

    你可以 等待 一个值并将其赋给一个变量

    const items = await getItems () // [ ... ]
    

    或者你可以 等待 另一表达式中的值

    items .concat (await getMoreItems ()) // [ ... ]
    

    这是因为 异步的 / 等待 形式 表达 它们可以与功能风格一起使用。如果你试图学习功能风格并避免 异步的 等待 因为你被误导了。如果 异步的 等待 如果只是命令式的话,这样的事情是不可能的

    const asyncUnfold = async (f, initState) =>
      f ( async (value, nextState) => [ value, ...await asyncUnfold (f, nextState) ]
        , async () => []
        , initState
        )
    

    真实的例子

    这里有一个实际的例子,我们有一个记录数据库,我们希望执行递归查找,或者其他什么…

    const data =
      { 0 : [ 1, 2, 3 ]
      , 1 : [ 11, 12, 13 ]
      , 2 : [ 21, 22, 23 ]
      , 3 : [ 31, 32, 33 ]
      , 11 : [ 111, 112, 113 ]
      , 33 : [ 333 ]
      , 333 : [ 3333 ]
      }
    

    异步函数 Db.getChildren 站在你和你的数据之间。如何查询节点和 全部的 它的后代?

    const Empty =
      Symbol ()
    
    const traverse = (id) =>
      asyncUnfold
        ( async (next, done, [ id = Empty, ...rest ]) =>
            id === Empty
              ? done ()
              : next (id, [ ...await Db.getChildren (id), ...rest ])
        , [ id ]
        )
    
    traverse (0)
    // => Promise [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]
    

    一个来自“javascript开发者天堂”的纯程序,用montes的话说。它是用函数表达式编写的,错误会相应地冒泡,我们甚至不需要接触 .then .

    我们可以使用命令式编写相同的程序。或者我们可以用 然后 也是。我们可以用各种方式来写,我想这就是重点所在 异步的 等待 形成的能力 表达 我们可以在各种样式中使用它们,包括功能样式。

    在下面的浏览器中运行整个程序

    const asyncUnfold = async (f, init) =>
      f ( async (x, acc) => [ x, ...await asyncUnfold (f, acc) ]
        , async () => []
        , init
        )
    
    const Db =
      { getChildren : (id) =>
          new Promise (r => setTimeout (r, 100, data [id] || []))
      }
    
    const Empty =
      Symbol ()
    
    const traverse = (id) =>
      asyncUnfold
        ( async (next, done, [ id = Empty, ...rest ]) =>
            id === Empty
              ? done ()
              : next (id, [ ...await Db.getChildren (id), ...rest ])
        , [ id ]
        )
        
    const data =
      { 0 : [ 1, 2, 3 ]
      , 1 : [ 11, 12, 13 ]
      , 2 : [ 21, 22, 23 ]
      , 3 : [ 31, 32, 33 ]
      , 11 : [ 111, 112, 113 ]
      , 33 : [ 333 ]
      , 333 : [ 3333 ]
      }
    
    traverse (0) .then (console.log, console.error)
    // => Promise
    // ~2 seconds later
    // [ 0, 1, 11, 111, 112, 113, 12, 13, 2, 21, 22, 23, 3, 31, 32, 33, 333, 3333 ]
        2
  •  0
  •   Chengyzh    6 年前

    我认为有两种方法。

    方案1

    继续使用Promise,但代码需要一些更改:

    const createRequest = ({ request, logger, terminal }) => ({ endpoint, timeout }) =>
      request.get(endpoint, { timeout })
        .then(response =>
          logger.info({ event: "Heartbeat request succeeded.", status: response.status })
        )
        .catch(err => {
          // after a catch the chain is restored
          return logger.error({ event: "Heartbeat request failed.", err })
        })
        .then(() => terminal.runAsync("pm2 restart myAPI"))
        .then(() => logger.info({ event: "Restarted API." }))
        .catch(err => logger.error({ event: "Failed to restart API.", err }))
    

    选项2

    使用await/async,将async更改为sync。