如何取消这些承诺的嵌套?

时间:2018-07-17 08:58:56

标签: javascript functional-programming promise es6-promise

背景

我有一个向服务器发出请求的功能。如果请求失败,我想: 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 } ) )
        );

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

问题?

我的问题是我的渔获物内部有一个Promise,这意味着我有嵌套。 现在,我强烈反对应许的嵌套,所以我真的想摆脱它,但我只是不知道如何。

问题

  1. 是否有可能摆脱catch内的嵌套承诺?
  2. 如果这样怎么办?

2 个答案:

答案 0 :(得分:3)

  

问题?

     

我的问题是我的渔获物内部有一个Promise,这意味着我有嵌套。现在,我强烈反对应许的嵌套,所以我真的想摆脱它,但我只是不知道如何。

     

– Flame_Phoenix

问题是您认为自己有问题,或者您是在StackOverflow而不是CodeReview上发布了此问题。 article you linked显示您对嵌套的Promise采取幼稚的观点

  

您会得到一整套彼此嵌套的承诺:

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

之所以这样做,是因为您需要对两个promise的结果做些事情,因此您无法链接它们,因为then()仅传递了前一个返回的结果。

     

您这样做的真正原因是因为您不了解Promise.all()方法。

     

–代码猴子http://taoofcode.net

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

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

嵌套诺言不是总是必需的,而是称其为“反模式”,并鼓励人们在知道差异是有害的之前就避免遵守诺言。


声明不起作用

other linked article显示了您可能再次被误导的地方

  

不要误会我的意思,异步/等待并不是世界上所有邪恶的源头。经过几个月的使用,我实际上学会了喜欢它。因此,如果您对编写命令性代码感到满意,那么学习如何使用async / await来管理异步操作可能是个不错的选择。

     

但是,如果您喜欢诺言,并且喜欢学习越来越多的功能性编程原理并将其应用于代码,则可能只想完全跳过异步/等待代码,而不必思考命令,而转向这种新的旧模式。 / p>      

–加布里埃尔·蒙特斯

仅这没有任何意义。如果您查看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声明,并且它永远不会求值–只能依赖于副作用。

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

dowhileswitch,甚至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" ]

asyncawait不是声明

您可以将异步函数分配给变量

const f = async x => ...

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

someFunc (async x => ... )

即使async函数什么也不返回,async仍然保证我们将收到Promise值

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

您可以await的值并将其分配给变量

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

或者您可以await在另一个表达式中输入一个值

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

这是因为async / await形成了表达式,它们可以与功能样式一起使用。如果您尝试学习功能样式并避免使用asyncawait,那仅仅是因为您被误导了。如果asyncawait仅是命令式风格,那么这样的事情将永远不可能

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开发人员的天堂”发送的一个纯程序,用蒙特斯的话来说就是。它是使用函数表达式编写的,错误相应地冒出,我们什至不必触摸.then

我们可以使用命令式风格编写相同的程序。或者我们也可以使用.then来编写功能样式。我们可以用各种方式来编写它,我想这就是重点–由于asyncawait具有形成表达式的能力,我们可以在各种形式下使用它们样式,包括功能样式。

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

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 ]

答案 1 :(得分:0)

我认为有两种方法。

选项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

使用等待/异步,将异步更改为同步。