You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Loopback框架中Error: Callback was already called问题求助

解决Loopback中"Callback was already called"错误的问题

咱们先拆解下你遇到的问题:你在Loopback的after save observer里用了async/await,同时又手动调用了next()回调,这就导致了回调被重复调用的错误。

问题根源

Loopback的observe方法对async函数和传统回调函数的处理逻辑是不一样的:

  • 如果你用传统的非async回调(带next参数),必须手动调用next()来通知Loopback继续执行流程,或者调用next(err)传递错误。
  • 但如果你把回调写成async函数,Loopback内部会自动把这个函数包装成Promise处理器:它会等待async函数执行完成,如果函数顺利执行完(没有抛出错误),Loopback会自动触发流程继续;如果函数抛出错误,Loopback会自动捕获并处理这个错误。

你现在的代码既用了async函数,又手动调用了next(),相当于同时触发了两次流程通知——一次是async函数执行完成后Loopback自动触发的,另一次是你手动调用next()触发的,所以就出现了Callback was already called的错误。

解决方法

只需要二选一:要么用传统回调,要么用async函数,不要混合使用。针对你的代码,最简洁的修正就是去掉next参数,不再手动调用next(),让async函数的Promise自动处理流程:

修正后的代码:

"use strict"; 
const Raven = require("raven"); 
Raven.config( "test" ).install(); 

module.exports = function(Reservation) { 
  function dateValidator(err) { 
    if (this.startDate >= this.endDate) { 
      err(); 
    } 
  } 

  function sendEmail(campground) { 
    return new Promise((resolve, reject) => { 
      Reservation.app.models.Email.send(formEmailObject(campground), function( err, mail ) { 
        if (err) { 
          console.log(err); 
          Raven.captureException(err); 
          reject(err); 
        } else { 
          console.log(mail); 
          console.log("email sent!"); 
          resolve(mail); 
        } 
      }); 
    }); 
  } 

  function formEmailObject(campground) { 
    return { 
      to: "loopbackintern@yopmail.com", 
      from: "noreply@optis.be", 
      subject: "Thank you for your reservation at " + campground.name, 
      html: "<p>We confirm your reservation for <strong>" + campground.name + "</strong></p>" 
    }; 
  } 

  Reservation.validate("startDate", dateValidator, { message: "endDate should be after startDate" }); 

  // 修正后的observe回调:移除next参数,不再手动调用next()
  Reservation.observe("after save", async function(ctx) { 
    try { 
      const campground = await Reservation.app.models.Campground.findById( ctx.instance.campgroundId ); 
      const mail = await sendEmail(campground); 
      // 不需要调用next(),async函数执行完毕后Loopback会自动继续流程
    } catch (e) { 
      Raven.captureException(e); 
      throw e; // 抛出错误,Loopback会自动捕获并处理
    } 
  }); 
};

对你之前尝试的解释

  • 把next()放在try/catch外部:不管是成功还是失败,你手动调用了next(),同时async函数的Promise完成时Loopback又会自动触发一次,所以还是会重复调用。
  • 仅保留catch中的next():当try块执行成功时,你没有手动调用next(),Loopback通过async函数的resolve自动处理了流程,所以没有错误;但这种写法不规范,因为你混合了两种模式,正确的做法是完全移除next相关的代码。

内容的提问来源于stack exchange,提问作者Jorge Belano Murphy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:33:36