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失效的问题 的相关文章

  • 如何重定向到 instagram://user?username={username}

    我的 html 页面上有这个链接 可以在特定用户上打开 Instagram 应用程序 a href Link to Instagram Profile a 我一直在寻找自动运行 url instagram user username USE
  • 使用 useReducers 调度函数发送多个操作?

    使用时是否可以通过调度函数发送多个动作useReducer挂钩反应 我尝试向它传递一组操作 但这会引发未处理的运行时异常 明确地说 通常会有一个初始状态对象和一个减速器 如下所示 const initialState message1 nu
  • 我想检查 $('#td1').text() === "x" 是否?

    我想检查innerHtml是否有X或O 所以我不能再次添加任何其他东西 但它不起作用 添加检查代码后它就停止了 我在这里尝试做一个简单的XO游戏来更熟悉javascript和jquery 我也不确定是否可以用 jQuery 做到这一点
  • jQuery AJAX 调用 Java 方法

    使用 jQuery AJAX 我们可以调用特定的 JAVA 方法 例如从 Action 类 该 Java 方法返回的数据将用于填充一些 HTML 代码 请告诉我是否可以使用 jQuery 轻松完成此操作 就像在 DWR 中一样 此外 对于
  • 使用 JavaScript 使链接保持活动状态并在单击时显示悬停效果

    I am struggling to make this work I d like to make it where if O F is clicked the hover state stays active if another li
  • Javascript正则表达式用于字母字符和空格? [关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions 我需要一个
  • 将div设置为隐藏,延时后可见

    我试图在 X 时间后 也许甚至在随机时间之后 但现在我们只做固定时间 在黑色背景上出现一个黄色方块 function initialSetup if document getElementById yellow null document
  • 在 webpack 2.x 中使用 autoprefixer 和 postcss

    如何使用autoprefixer使用 webpack 2 x 以前 它曾经是这样的 module loaders test scss loader style css sass postcss postcss gt return autop
  • Nodejs 在循环中等待

    我想循环等待 实现此目的的最佳方法是什么 这是我的实际代码 var groups 461 6726 3284 4 121 11 399 1735 17 19 1614 groups forEach function value myfunc
  • 如何在react-native中获取Text组件的onPress值

    我是一名新的 React Native 开发人员 我想使用 onPress 获取 Text 组件的值并将其传递给函数
  • 提交表单并重定向页面

    我在 SO 上看到了很多与此相关的其他问题 但没有一个对我有用 我正在尝试提交POST表单 然后将用户重定向到另一个页面 但我无法同时实现这两种情况 我可以获取重定向或帖子 但不能同时获取两者 这是我现在所拥有的
  • node.js 本身还是 nginx 前端来提供静态文件?

    是否有更快的基准或比较 将 nginx 放在节点前面并让它直接提供静态文件或仅使用节点并使用它提供静态文件 nginx 解决方案似乎对我来说更易于管理 有什么想法吗 我不得不不同意这里的答案 虽然 Node 可以做得很好 但如果配置正确 n
  • 如何使用tampermonkey模拟react应用程序中的点击?

    我正在尝试使用 Tampermonkey 脚本模拟对 React 元素的点击 不幸的是 由于 React 有自己的影子 DOM 所以天真的方法使用document querySelector 不工作 我遇到了一些需要修改 React 组件本
  • Laravel 中只向登录用户显示按钮

    如果我以 John 身份登录 如何才能只显示 John 的红色按钮而不显示 Susan 的红色按钮 测试系统环境 Win10 Laravel5 4 Mysql5 7 19 table class table table responsive
  • HTML 离线应用程序缓存,列出下载的文件

    作为我正在构建的离线 Web 应用程序的加载屏幕的一部分 使用缓存清单 http developer apple com library safari documentation iPhone Conceptual SafariJSData
  • 有没有办法阻止 prettier / prettier-now 将函数参数分解为新行

    当使用 prettier prettier now 在保存时进行格式化时 当一个函数包装另一个函数时 它会中断到一个新行 我想知道是否有办法阻止这种行为 例如 期望的输出 app get campgrounds id catchAsync
  • 如何更改此 jquery 插件的时区/时间戳?

    我正在使用这个名为 timeago 的插件 在这里找到 timeago yarp com 它工作得很好 只是它在似乎不同的时区运行 我住在美国东部 费城时区 当我将准确的 EST 时间放入 timeago 插件时 比如 2011 05 28
  • 如何在 pg-promise 中设置模式

    我正在搜索的文档pg 承诺 https github com vitaly t pg promise特别是在创建客户端时 但我无法找到设置连接中使用的默认架构的选项 它始终使用public架构 我该如何设置 通常 为数据库或角色设置默认架构
  • 如何获取浏览器视口中当前显示的内容

    如何获取当前正在显示长文档的哪一部分的指示 例如 如果我的 html 包含 1 000 行 1 2 3 9991000 并且用户位于显示第 500 行的中间附近 那么我想得到 500 n501 n502 或类似的内容 显然 大多数场景都会比
  • 如何从图像输入中获取 xy 坐标?

    我有一个输入设置为图像类型

随机推荐

  • 有一对兔子,从出生后第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