基于先前调用的响应的异步调用,避免回调地狱

Posted

技术标签:

【中文标题】基于先前调用的响应的异步调用,避免回调地狱【英文标题】:Async call based on a response from a previous call, avoiding callback hell 【发布时间】:2018-12-20 16:14:08 【问题描述】:

所以我通常使用.then 链来处理异步函数。但这一次并没有解决它。情况就是这样。

return Promise.all([
    Banner.findOne(),
    event.findOne(
        status: true
    ),
    Account.find(
        accType: 'employer'
    )
        .then(([banner, event, account]) => 
            res.render('index', 
                title: 'Web Project Studio',
                user: req.user,
                banner: banner,
                event: event,
                employer: account
            );
        ).catch(e => 
        console.error(e);
    )
]);

所以这很有效,一切都很好。但现在我决定改变选择活跃event的方式。我正在调用一个名为 generalInfo 的集合,并从那里获取当前事件的 ID。到目前为止,这是我所拥有的:

return Promise.all([
    Banner.findOne(),
    GeneralInfo.findOne(),
    Account.find(
        accType: 'employer'
    )
]).then(([banner, generalInfo, account]) => 
        Event.findById(generalInfo.activeEventId).then(r => 
            console.log('test');
            var event = r;
            res.render('index', 
                title: 'Web Project Studio',
                user: req.user,
                banner: banner,
                event: event,
                employer: account
            );
        ).catch(e => console.log(e));
    
).catch(e => 
    console.error(e);
)

但是这段代码开始看起来像一个回调地狱。

我也试过这样的:

var banner = await Banner.findOne().catch(e => console.log(e));
var currentEvent = await GeneralInfo.findOne().catch(e => console.log(e));
currentEvent.then(async r => 
    var event = await Event.findOneById(r.activeEventId).catch(e => console.log(e));
).catch(e => console.log(e));

它还没有完成,但它有点显示我的思维方式。但同样,没有运气。

那么我怎样才能在不永远链接 .then 的情况下与 async 相处呢?我需要将所有返回的对象传递给render 函数。

【问题讨论】:

在我发布答案之前,我很好奇你是否愿意从你的承诺(或承诺)中创建一个可观察的。如果是这样,您可以与 map、concat 和所有那些让您以类似程序(但当然是异步)的方式执行此类操作的出色的可观察运算符合并。如果你对 rxjs 不感兴趣,尽管我会保留它。 我不知道那是什么,但我完全愿意提供任何答案 啊:你真的(真的!)需要查找“rxjs”并探索“observables”。基本上,它们允许您将数据表示为可以与其他流合并的“流”,等待其他流准备好,等等。 Promise 有利于它们的优点(几乎是即时的一次性执行),但是当您需要管理多个异步流时,特别是如果它们需要按顺序管理、加入、重试和很快。这需要一些努力,但现在,如果你在做异步工作,你真的需要 rxjs。 无论如何,一个 Observable(表示异步数据流的东西)可以包装一个 Promise,以便您可以使用 Observable 运算符等。一定要查找“rxjs 中的多个流”,您将获得各种教程和代码示例,了解如何去做。 会的。谢谢! 【参考方案1】:

您可以更早地嵌套,这也具有重叠更多的优点(请参阅*** 评论):

return Promise.all([
    Banner.findOne(),
    GeneralInfo.findOne()
        .then(generalInfo => Event.findById(generalInfo.activeEventId)), // ***
    Account.find(
        accType: 'employer'
    )
).then(([banner, event, account]) => 
    res.render('index', 
        title: 'Web Project Studio',
        user: req.user,
        banner: banner,
        event: event,
        employer: account
    );
).catch(e => 
    console.error(e);
);

如果你经常做 generalInfo => 事件的事情,你可以把它包装在一个实用函数中。

或者,如果它们不应该那样重叠,您可以通过添加另一个 then 来最小化嵌套:

return Promise.all([
    Banner.findOne(),
    GeneralInfo.findOne(),
    Account.find(
        accType: 'employer'
    )
]).then(([banner, generalInfo, account]) => 
    return Event.findById(generalInfo.activeEventId).then(event =>  // ***
        return [banner, event, account];                             // ***
    );                                                              // ***
).then(([banner, event, account]) => 
    res.render('index', 
        title: 'Web Project Studio',
        user: req.user,
        banner: banner,
        event: event,
        employer: account
    );
).catch(e => 
    console.error(e);
)

【讨论】:

【参考方案2】:

这应该可行。

return Promise.all([
  Banner.findOne(),
  GeneralInfo.findOne(),
  Account.find(
    accType: 'employer'
  )
]).then(async ([banner, generalInfo, account]) => 
    res.render('index', 
      title: 'Web Project Studio',
      user: req.user,
      banner: banner,
      event: await Event.findById(generalInfo.activeEventId),
      employer: account
    );
).catch(e => 
    console.error(e);
)

【讨论】:

以上是关于基于先前调用的响应的异步调用,避免回调地狱的主要内容,如果未能解决你的问题,请参考以下文章

promise处理回调地狱

从地狱到天堂,Node 回调向 async/await 转变

微信小程序api怎么promise化

使用 RxSwift 同步异步网络调用

Promise,async/await解决回调地狱

进阶学习5:JavaScript异步编程——同步模式异步模式调用栈工作线程消息队列事件循环回调函数