Koa-router异步返回ctx.body失效的问题

2023-11-11

情景复现

router.put('/category/:id', (ctx, next) => {
  const data = ctx.request.body
  db.updateCategoryById(ctx.params.id, data)
    .then((doc) => {
      if (doc) ctx.body = { status: 0, message: '修改参数成功' }
      else ctx.body = { status: -2, message: 'ID错误, 无法找到数据' }
    })
})

上面的例子中, 处理请求时通过mongoose向MongoDB读取数据, 读取方法返回一个Promise, 所以在then()中为ctx.body赋值, 返回查询到的数据

存在的问题

实际发送请求, 发现then()执行了, 但是前端没有收到任何返回的数据

百度结果

照例百度, 基本上都是说加一层Promise就可以, 但是为什么呢?

问题分析

  1. router.get()方法的回调函数必须返回Promise, 所以需要显式return new Promise或者为回调方法加async修饰符 [错]
  2. router.get()方法调用回调函数的then()来添加异步任务到微任务队列 [不精确]
  3. ctx.body赋值的执行一定要早于router.get()调用回调函数(这个表述不精确, 请看后面示例中的说明)

问题解决(伪)

根据问题分析, 可以把上面的代码修改为如下几种方式:

  1. 显式返回Promise
router.put('/category/:id', (ctx, next) => {
  const data = ctx.request.body
  return db.updateCategoryById(ctx.params.id, data)
    .then((doc) => {
      if (doc) ctx.body = { status: 0, message: '修改参数成功' }
      else ctx.body = { status: -2, message: 'ID错误, 无法找到数据' }
    })
})
  1. 使用async/await
router.put('/category/:id', async (ctx) => {
  const data = ctx.request.body
  try {
    const doc = await db.updateCategoryById(ctx.params.id, data)
    if (doc) ctx.body = { status: 0, message: '修改参数成功' }
    else ctx.body = { status: -2, message: 'ID错误, 无法找到数据' }
  } catch (e) {
    ctx.body = { status: -1, message }
  }
})

实际情况

在本例中, 遇到的是一种特殊情况

虽然mongoose返回的是一个Promise, 但不是原生的Promise, 而是BlueBird.js实现的(官网说性能优于原生Promise), 其内部实现中, 在nodejs环境下, 实际使用的是setImmediate来触发一个异步任务. 在nodejs中, setImmediate的执行顺序要晚于原生Promise, 所以就触发了问题分析中的第3条: 执行顺序.
在本例中, 就是router的回调Promise函数先执行完毕(ctx已经发出), 然后执行的mongoose方法返回的"Promise"任务, 而此时ctx已经发出, 所以赋值无效.

下面两段代码也可以证明:

router.put('/category/:id', (ctx, next) => {
  const data = ctx.request.body
  console.log({res: db.updateCategoryById(ctx.params.id, data)})
  db.updateCategoryById(ctx.params.id, data).then((doc) => {
    console.log('123');
  })
  new Promise((resolve) => {
    resolve()
  }).then(() => {
    console.log('456');
  })
})
  • 实际先输出’456’, 然后输出’123’
router.put('/category/:id', (ctx, next) => {
  new Promise((resolve) => {
    resolve()
  }).then(() => {
    console.log('456');
    ctx.body = { status: 0, message: '修改参数成功234' }
  })
})
  • 虽然回调方法没有返回Promise, 而且没有async修饰, 但是前端仍然可以收到ctx.body的数据

结论

  • 为什么问题分析(1)是错的?

    • 查看router源码可知, 它并没有判断回调函数是不是Promise, 而是在请求成功或异常的时候, 调用Promise.resolve(fn())/reject(fn()), 即调用一次回调函数, 把回调函数的结果放入一个Promise任务
  • 为什么问题分析(2)是不准确的?

    • 因为确实有Promise任务添加了, 但是不是通过回调函数的then()方法
  • 为什么在前面的问题解决加个伪字呢?

    • 因为那只是解决了表面现象, 并没有体现真实原因, 纵观网上的解决方案, 其实他们的应用场景基本都是requestsetTimeout等, 这些触发的异步都是宏任务, 而原生Promise是微任务, 所以产生了执行顺序问题
  • 为什么解决方法1可行呢?

    • 因为Promise.then()又返回了一个Promise, 根据Promise的链式调用执行顺序, 第二个then必然晚于第一个then的执行, 所以ctx.body仍然可以先执行
  • 为什么加了async/await就可以让setImmediate先执行呢?

    • 解决方法2的代码可以用下面的代码来模拟:
new Promise(async (resolve) => {
  const a = await new Promise((res) => {
    setImmediate(() => {
      console.log(123)
      res(789)
    })
  })
  console.log(a)
  resolve()
}).then(() => console.log(456))

外层Promise是加了async的回调函数, 里面的setImmediate是mongoose的异步方法, 而await让外层Promise的resolve()在setImmediate的异步返回后才被调用, 这时才真正触发了外层Promise的异步执行(在例子中就是执行ctx.body)

所以, 关键不是Promise的问题, 而是nodejs的任务执行顺序问题

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Koa-router异步返回ctx.body失效的问题 的相关文章

  • jQuery 中的 Javascript .files[0] 属性

    jQuery 中是否有与此语句等效的语句 var value document getElementById id files 0 使用附加 files 0 的标准 jQuery 选择器似乎不起作用 并且我找不到与 files 等效的 jQ
  • MongoDB 仅插入唯一电子邮件条件不起作用

    在我的猫鼬模式中 我有以下两个字段 username type String required true user name required trim true unique true email type String required
  • 使用 Javascript 在 Imacros 中循环

    我如何使用 javascript 循环 imm imacros 脚本 我搜索了一下 发现了这个 for i 0 i lt n i iimPlay marconame iim 但当我使用它时 我的浏览器 Firefox 18 挂起 for i
  • 如何通过 HTML 按钮播放声音

    我目前通过网站播放音乐的方法是通过 HTML 音频标签 不过我希望能够通过 HTML 按钮来播放它 该按钮应该能够在播放和停止之间切换音乐 我在 JSFiddle 创建了一个示例 但不知道如何实现它 有人可以告诉我如何使用我的 JSFidd
  • 在浏览器中语音聊天? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我们正在寻求建立一个小组 voice 使用服务器上的node js 在浏览器中聊天 这可能吗 如果您希望您的解决方案是基于服务器端和客
  • 在 BIRT 中输入参数后更新数据集查询

    在 BIRT 报告设计中传递参数后 如何更改或更新数据集的查询 详细说明 我有一个如下所示的查询 WHERE 该参数标记可以保存不同的值 在用户输入参数后 它看起来像这样 例如 WHERE column name 1 or WHERE co
  • Flask wtf.quick_form 运行一些 javascript 并设置表单变量

    我正在创建博客文章 到目前为止已经使用普通的 html 表单完成了 我所做的一个有趣的想法是运行 javascript onclick 并使用页面中的额外数据在表单中设置一个隐藏变量 这很好地传递到服务器并通过 request form 获
  • JavaScript 动画平滑滚动

    默认情况下 当您有这样的片段链接时 a href some url some fragment some text a 浏览器立即向下滚动到该片段 我该如何编程才能使用标准 JS 顺利地向下移动到该片段 这是一个例子 Example htt
  • 使react-leaflet能够离线使用

    我一直在使用反应传单 https github com PaulLeCam react leaflet图书馆 到目前为止运作良好 现在我希望网站预加载尽可能多的图块 以便网络应用程序 也是 PWA 可以在没有互联网的情况下使用 我找到了一些
  • Aptana Studio 3 上的预览选项卡在哪里?

    我在 Windows PC 上使用 Aptana Studio 2 并有一个选项卡用于在 IE 上预览页面 另一个选项卡用于在 Firefox 上预览 但我切换到了 Aptana 3 我不知道是没有预览还是我没有找到它 是的 我在 stac
  • 访问 TypeScript 数组的最后一个元素

    TypeScript 中有访问数组最后一个元素的符号吗 在 Ruby 中我可以说 array 1 有类似的东西吗 您可以通过索引访问数组元素 数组中最后一个元素的索引将是数组的长度 1 因为索引是从零开始的 这应该有效 var items
  • 如何清除WebGL中的矩形区域?

    WebGL 有一个clear清除整个表面的方法 清除表面的特定矩形的最佳方法是什么 例如 我想将一个从 50 50 开始的 100x100 像素框设置为全零 ARGB 0 0 0 0 我现在能想到的就是用一个写入零的片段着色器绘制一个四边形
  • Express URIError:无法解码参数

    当请求的参数包含时 我将 next js 与自定义 Express 服务器一起使用 它会导致此错误 URIError Failed to decode param faker at decodeURIComponent
  • 如何访问另一个 mobx 商店中的 mobx 商店?

    假设以下结构 stores RouterStore js UserStore js index js each of Store jsfiles 是一个 mobx 存储类 包含 observable and action index js只
  • 是否可以使用打字稿映射类型来创建接口的非函数属性类型?

    所以我正在研究 Typescript 的映射类型 是否可以创建一个接口来包装另一种类型 从而从原始类型中删除函数 例如 interface Person name string age number speak void type Data
  • 如何在网页上实现文件上传进度条?

    当用户将文件上传到我的网络应用程序时 我想显示比动画 gif 更有意义的内容 我还有哪些可能性 编辑 我正在使用 Net 但我不介意是否有人向我展示与平台无关的版本 如果您对这一切在客户端通常如何工作感兴趣 就是这样 所有解决方案都通过 J
  • 如何在 e2e AngularJS 测试中进行文件上传?

    在我的一种观点中 我有一个文件上传控件 它支持通过拖放或单击按钮后打开的标准文件对话框上传文件 How to do this in my e2e tests1 1 Just one of the two options will be en
  • window.showModalDialog 的等效跨浏览器解决方案是什么?

    window showModalDialog 的等效跨浏览器解决方案有哪些 showModalDialog 在 IE 和 FF 3 中引入 我个人认为没有 但是有很多 UI 工具包提供了这样的功能 例如jQuery UI http jque
  • React Native - 跨屏幕传递数据

    我遇到了一些麻烦react native应用程序 我不知道如何跨屏幕传递数据 我意识到还有其他类似的问题在 SO 上得到了回答 但是这些解决方案对我来说不起作用 我正在使用StackNavigator 这是我的设置App js file e
  • 如何修复超出最大调用堆栈大小

    有一个 MERN Firebase 应用程序并收到此错误和一堆 atdeepExtend deepCopy ts 71 RangeError Maximum call stack size exceeded getApps as apps

随机推荐

  • 有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?

    分析 兔子的对数从第一月开始 1 1 2 3 5 8 规则 从第三月开始 每月的对数是前两月之和 题目问每个月的兔子总数 为更好理解 在此指定具体月数 改为求第20月的兔子总数 本题分别运用三种的方法实现 数组实现 用变量的变化实现 递归实
  • windows上删除不了文件

    遇到文件或者文件夹无法删除或者移动 其实本质是因为有应用或者其他软件在打开它 所以会导致我们无法更改他的位置 解决这个的办法就是把相应的软件关闭掉 把文件释放出来 然后我们就可以正常移动或者删除了 方法 步骤 遇到问题的情况 文件被使用无法
  • MySQL数据库总结 之 函数命令总结

    MySQL命令语句中的函数包含四种 字符串函数 数值函数 日期函数 流程函数 前两篇关于MySQL的博客 地址如下 MySQL数据库 SQL语言命令总结 数据类型 运算符和聚合函数汇总 Flying Bulldog的博客 CSDN博客htt
  • (附源码)计算机毕业设计SSM疫情隔离便民系统

    项目运行 环境配置 Jdk1 8 Tomcat7 0 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 SSM mybatis Ma
  • 字符串中找出连续最长数字串(两种题型)--C++

    题目描述一 读入一个字符串str 输出字符串str中的连续最长的数字串 输入描述 个测试输入包含1个测试用例 一个字符串str 长度不超过255 输出描述 在一行内输出str中里连续最长的数字串 输入 abcd12345ed125ss123
  • 安装xposed(解决xposed问题)

    科学上网可轻松解决本文的问题 经过测试leidian mumu yeshen三个模拟器的最新版本只有leidian安装完成后可以重启 其他两个均会卡99 模拟器再起不能 MuMu模拟器win版 版本 2 1 3 可以 安装xposed前需关
  • 面试必备—MySQL中数据查询语句

    一 基本概念 查询语句 基本语句 1 select from 表名 可查询表中全部数据 2 select 字段名 from 表名 可查询表中指定字段的数据 3 select distinct 字段名 from 表名 可对表中数据进行去重查询
  • 使用XStream实现Java对象与XML互相转换(不断更新中)

    添加pom依赖
  • 学习周报-2023-0210

    文章目录 一 在SUSE11sp3系统中将openssh从6升级到8 一 需求 二 系统环境 三 部署流程 1 上传编译安装的软件包 2 安装 gcc编译软件 3 安装依赖zlib 4 安装依赖openssl 5 安装openssh 二 在
  • 华为OD机试真题- 战场索敌-2023年OD统一考试(B卷)

    题目描述 有一个大小是NxM的战场地图 被墙壁 分隔成大小不同的区域 上下左右四个方向相邻的空地 属于同一个区域 只有空地上可能存在敌人 E 请求出地图上总共有多少区域里的敌人数小于K 输入描述 第一行输入为N M K N表示地图的行数 M
  • 8-js高级-2

    JavaScript 进阶 2 了解面向对象编程的基础概念及构造函数的作用 体会 JavaScript 一切皆对象的语言特征 掌握常见的对象属性和方法的使用 深入对象 内置构造函数 综合案例 深入对象 了解面向对象的基础概念 能够利用构造函
  • TesseractEngine

    URL http download csdn net download fuxuan928 4068683 GOOGLE https code google com p tesseractdotnet 下面识别OCR验证码用 NET来实现
  • 使用Python爬虫定制化开发自己需要的数据集

    在数据驱动的时代 获取准确 丰富的数据对于许多项目和业务至关重要 本文将介绍如何使用Python爬虫进行定制化开发 以满足个性化的数据需求 帮助你构建自己需要的数据集 为数据分析和应用提供有力支持 1 确定数据需求和采集目标 在开始定制化开
  • QT学习—五种直接连接信号槽的连接方式

    一 信号与槽机制 特别鸣谢B站大轮明王讲Qt的讲解 大轮明王讲Qt的个人空间 哔哩哔哩 bilibili 信号与槽机制 Signal and Slot 是一种在软件开发中广泛使用的通信机制 主要用于处理事件驱动的程序设计 它是Qt框架中的一
  • 使用 Spot 低成本运行 Job 任务

    作者 代志锋 云果 阿里云技术专家 导读 本节课程有三部分内容 首先阐述 ECI 支持成本优化的几种方式 然后重点介绍 Spot 实例是什么以及如何采用 Spot 实例进行成本优化 最后总结 Spot 实例支持的场景以及注意事项 成本优化
  • 基于JWT token认证机制和基于session认证机制

    基于session认证机制 http协议本身是一种无状态的协议 而这就意味着如果用户通过应用向服务器提供了用户名和密码进行认证 下一次请求时 用户还要再一次进行用户认证 因为根据http协议 服务器并不知道是哪个用户发出的请求 所以 为了识
  • 易语言服务器客户端网络验证,超强网络验证系统附远程服务支持库

    这套网络验证我自己用了好几年 也是在几年前开发的 并且完整开源的源码 如果真 超级列表框 取表项数 0 信息框 先读取要导出的充值卡信息 48 提示 返回 如果真结束 如果真 信息框 是否要导出选列表框中 到文本 超级列表框 取表项数 条数
  • openwrt运行linux软件,OpenWrt运行go程序(交叉编译)-Go语言中文社区

    OpenWrt运行go程序 交叉编译 引言 因项目要求 需要在openwrt系统上运行http服务 由于对openwrt自带的uhttpd服务器及luci不熟悉 所以决定采用go语言来实现http服务 以下是配置go的过程以及踩过的一些坑
  • 【AI人工智能】 iTab浏览器标签页中最强大的AI功能莫过于此了, 你不用真的太可惜了! 最后一步就这样干(3)

    个人主页 极客小俊 作者简介 web开发者 设计师 技术分享博主 希望大家多多支持一下 我们一起进步 如果文章对你有帮助的话 欢迎评论 点赞 收藏 加关注 集成使用AI功能 接着我们打开Chrome浏览器 你就会发现标签页变成了iTab专属
  • Koa-router异步返回ctx.body失效的问题

    情景复现 router put category id ctx next gt const data ctx request body db updateCategoryById ctx params id data then doc gt