前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# async/await封装使用mysql

30 秒速记

  • 目标:把回调风格的 connection.query 包成返回 Promise 的 query(sql, values),业务里直接 await
  • 三种做法:手写 new Promise / util.promisify(pool.query).bind(pool) / 直接用 mysql2/promise
  • await 出错会抛异常,要用 try/catch 接,或者交给 Koa 的错误中间件统一处理
  • 互不依赖的查询用 Promise.all 并发,别一个个 await 排队
  • await 只是写法像同步,底层还是异步,不会阻塞事件循环

async/await 封装就是把 mysql 的回调接口包成 Promise,这样查数据库能写成 const rows = await query(sql),读起来像同步代码。 好处不只是好看:多步查询不用嵌套回调,错误可以用 try/catch 一处接住,在 Koa 里还能直接被错误中间件捕获。现在其实不太需要自己手写封装了,mysql2/promise 原生就支持。要留意的是,几个互不相关的查询别依次 await,用 Promise.all 一起发会快很多。

理解「async/await封装使用mysql」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 event-loop 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

把回调包成 Promise 有三种写法,效果一样,复杂度递减:

// 写法一:手写 Promise(理解原理用)
const query = (sql, values) =>
  new Promise((resolve, reject) => {
    pool.query(sql, values, (err, rows) => (err ? reject(err) : resolve(rows)))
  })

// 写法二:util.promisify,一行搞定
const util = require('util')
const query2 = util.promisify(pool.query).bind(pool) // 不 bind 会丢 this

// 写法三:直接换 mysql2/promise,不用封装
const mysql = require('mysql2/promise')
const pool3 = mysql.createPool({ /* ... */ })
const [rows] = await pool3.query('SELECT 1 AS ok') // rows: [ { ok: 1 } ]
@前端进阶之旅: 代码已经复制到剪贴板

用起来都是这样:

async function getUserPage(id) {
  // 两个查询互不依赖,并发执行
  const [users, posts] = await Promise.all([
    query('SELECT * FROM user WHERE id = ?', [id]),
    query('SELECT * FROM post WHERE user_id = ? LIMIT 10', [id]),
  ])
  return { user: users[0], posts }
}
@前端进阶之旅: 代码已经复制到剪贴板

async/await 让代码「看起来」像同步,但每个 await 都是一次异步等待。理解这一点,就不会担心它阻塞服务,也会主动把可以并行的查询放进 Promise.all。

面试官追问

追问 1await 一个查询的时候,Node 是不是就卡住了,处理不了别的请求?
参考回答

不会。await 只是把当前函数后面的代码挂起,事件循环照常处理其他请求,等查询结果回来再继续。真正会卡住的是 fs.readFileSync 这类同步 API。

追问 2一个页面要查用户信息和文章列表,两个 await 写下来慢了一倍,怎么改?
参考回答

两个查询互不依赖,就用 const [user, posts] = await Promise.all([getUser(id), getPosts(id)]) 并发发出去,总耗时取决于慢的那个。

追问 3封装后的 query 出错了,在 Koa 里怎么统一处理?
参考回答

在最外层写一个错误中间件:try { await next() } catch (err) { ctx.status = 500; ctx.body = { message: '服务异常' } },再按 err.code 细分。业务里不用每处都 try/catch。

追问 4手写封装、util.promisify、mysql2/promise 怎么选?
参考回答

新项目直接 mysql2/promise,零封装。老项目已经用着 mysql,util.promisify(pool.query).bind(pool) 一行搞定,注意要 bind,不然 this 丢了会报错。

追问 5for 循环里 await 插入 1000 条数据很慢,怎么优化?
参考回答

逐条 await 就是 1000 次往返。改成一条批量插入:INSERT INTO user (email) VALUES ?,参数传二维数组 [[a], [b]],一次搞定。

# async/await封装使用mysql

# 前言

30 秒速记

  • 痛点:mysql 的操作全是回调,多步查询会嵌套成回调地狱,错误要每层手动传
  • 思路:包一层 Promise,配合 async/await 写成同步的样子
  • await 只暂停当前 async 函数,不阻塞事件循环,其他请求照常处理
  • Koa 2 中间件本身就是 async 函数,数据库层返回 Promise 才能自然地 await

这一章要解决的问题就是回调写法太难读,用 Promise 包一下,再配上 async/await,数据库操作就能按顺序往下写。 比如「查用户 → 查他的订单 → 查订单商品」,回调写法要嵌三层,每层都要判 err;改成 await 之后就是三行,出错统一被 try/catch 接住。在 Koa 2 里这一点尤其重要,中间件都是 async 函数,数据库层返回 Promise 才能和它配合起来。

由于mysql模块的操作都是异步操作,每次操作的结果都是在回调函数中执行,现在有了async/await,就可以用同步的写法去操作数据库

理解「前言」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1说「用同步的写法操作数据库」,那数据库操作变成同步的了吗?
参考回答

没有,只是写法像同步。await 后面的代码会被放到 Promise 完成之后执行,等待期间线程照样去处理别的事。

追问 2回调写法嵌三层,哪里最容易出 bug?
参考回答

错误处理。每层都要写 if (err) return cb(err),漏一层错误就被吞掉了,或者回调被调用两次。Promise 链里任何一步 reject 都会直接跳到 catch。

追问 3Koa 2 里如果数据库层还是回调风格,会出什么问题?
参考回答

中间件 await next() 之后就返回了,回调还没执行,ctx.body 没赋值,客户端拿到 404。数据库层必须返回 Promise,中间件 await 它,Koa 才知道要等。

追问 4async 函数里忘了写 await,会怎样?
参考回答

拿到的是一个 Promise 对象而不是查询结果,rows.length 是 undefined。而且这个 Promise 如果 reject,没人接住就是一个未处理的 rejection。

# Promise封装mysql模块

30 秒速记

  • 封装:new Promise 里 pool.getConnection → connection.query → 成功 resolve(rows)、失败 reject(err) → connection.release()
  • release 要放在 resolve / reject 之外、无论成败都执行;getConnection 失败时没有连接可还
  • 简单查询其实可以直接用 pool.query,它内部就是借连接、查询、还连接
  • 第二个参数 values 用来填 ? 占位符,防注入全靠它
  • 调用方 await query(sql, values),出错用 try/catch 接

Promise 封装就是在回调里调 resolve 和 reject,把「借连接、执行 SQL、还连接」包成一个能 await 的函数。 写这种封装最重要的是连接一定要还:不管查询成功还是失败,connection.release() 都要执行,不然连接池很快被借光,所有请求都卡在等连接上。另外这个函数其实就是 pool.query 加一层 Promise,所以更简洁的写法是 new Promise 里直接调 pool.query,或者 util.promisify。

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

async/await封装使用mysql|Nodejs专题系列

# async/await封装使用mysql

30 秒速记

  • 目标:把回调风格的 connection.query 包成返回 Promise 的 query(sql, values),业务里直接 await
  • 三种做法:手写 new Promise / util.promisify(pool.query).bind(pool) / 直接用 mysql2/promise
  • await 出错会抛异常,要用 try/catch 接,或者交给 Koa 的错误中间件统一处理
  • 互不依赖的查询用 Promise.all 并发,别一个个 await 排队
  • await 只是写法像同步,底层还是异步,不会阻塞事件循环

async/await 封装就是把 mysql 的回调接口包成 Promise,这样查数据库能写成 const rows = await query(sql),读起来像同步代码。 好处不只是好看:多步查询不用嵌套回调,错误可以用 try/catch 一处接住,在 Koa 里还能直接被错误中间件捕获。现在其实不太需要自己手写封装了,mysql2/promise 原生就支持。要留意的是,几个互不相关的查询别依次 await,用 Promise.all 一起发会快很多。

理解「async/await封装使用mysql」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 event-loop 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

把回调包成 Promise 有三种写法,效果一样,复杂度递减:

// 写法一:手写 Promise(理解原理用)
const query = (sql, values) =>
  new Promise((resolve, reject) => {
    pool.query(sql, values, (err, rows) => (err ? reject(err) : resolve(rows)))
  })

// 写法二:util.promisify,一行搞定
const util = require('util')
const query2 = util.promisify(pool.query).bind(pool) // 不 bind 会丢 this

// 写法三:直接换 mysql2/promise,不用封装
const mysql = require('mysql2/promise')
const pool3 = mysql.createPool({ /* ... */ })
const [rows] = await pool3.query('SELECT 1 AS ok') // rows: [ { ok: 1 } ]
@前端进阶之旅: 代码已经复制到剪贴板

用起来都是这样:

async function getUserPage(id) {
  // 两个查询互不依赖,并发执行
  const [users, posts] = await Promise.all([
    query('SELECT * FROM user WHERE id = ?', [id]),
    query('SELECT * FROM post WHERE user_id = ? LIMIT 10', [id]),
  ])
  return { user: users[0], posts }
}
@前端进阶之旅: 代码已经复制到剪贴板

async/await 让代码「看起来」像同步,但每个 await 都是一次异步等待。理解这一点,就不会担心它阻塞服务,也会主动把可以并行的查询放进 Promise.all。

面试官追问

追问 1await 一个查询的时候,Node 是不是就卡住了,处理不了别的请求?
参考回答

不会。await 只是把当前函数后面的代码挂起,事件循环照常处理其他请求,等查询结果回来再继续。真正会卡住的是 fs.readFileSync 这类同步 API。

追问 2一个页面要查用户信息和文章列表,两个 await 写下来慢了一倍,怎么改?
参考回答

两个查询互不依赖,就用 const [user, posts] = await Promise.all([getUser(id), getPosts(id)]) 并发发出去,总耗时取决于慢的那个。

追问 3封装后的 query 出错了,在 Koa 里怎么统一处理?
参考回答

在最外层写一个错误中间件:try { await next() } catch (err) { ctx.status = 500; ctx.body = { message: '服务异常' } },再按 err.code 细分。业务里不用每处都 try/catch。

追问 4手写封装、util.promisify、mysql2/promise 怎么选?
参考回答

新项目直接 mysql2/promise,零封装。老项目已经用着 mysql,util.promisify(pool.query).bind(pool) 一行搞定,注意要 bind,不然 this 丢了会报错。

追问 5for 循环里 await 插入 1000 条数据很慢,怎么优化?
参考回答

逐条 await 就是 1000 次往返。改成一条批量插入:INSERT INTO user (email) VALUES ?,参数传二维数组 [[a], [b]],一次搞定。

# async/await封装使用mysql

# 前言

30 秒速记

  • 痛点:mysql 的操作全是回调,多步查询会嵌套成回调地狱,错误要每层手动传
  • 思路:包一层 Promise,配合 async/await 写成同步的样子
  • await 只暂停当前 async 函数,不阻塞事件循环,其他请求照常处理
  • Koa 2 中间件本身就是 async 函数,数据库层返回 Promise 才能自然地 await

这一章要解决的问题就是回调写法太难读,用 Promise 包一下,再配上 async/await,数据库操作就能按顺序往下写。 比如「查用户 → 查他的订单 → 查订单商品」,回调写法要嵌三层,每层都要判 err;改成 await 之后就是三行,出错统一被 try/catch 接住。在 Koa 2 里这一点尤其重要,中间件都是 async 函数,数据库层返回 Promise 才能和它配合起来。

由于mysql模块的操作都是异步操作,每次操作的结果都是在回调函数中执行,现在有了async/await,就可以用同步的写法去操作数据库

理解「前言」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1说「用同步的写法操作数据库」,那数据库操作变成同步的了吗?
参考回答

没有,只是写法像同步。await 后面的代码会被放到 Promise 完成之后执行,等待期间线程照样去处理别的事。

追问 2回调写法嵌三层,哪里最容易出 bug?
参考回答

错误处理。每层都要写 if (err) return cb(err),漏一层错误就被吞掉了,或者回调被调用两次。Promise 链里任何一步 reject 都会直接跳到 catch。

追问 3Koa 2 里如果数据库层还是回调风格,会出什么问题?
参考回答

中间件 await next() 之后就返回了,回调还没执行,ctx.body 没赋值,客户端拿到 404。数据库层必须返回 Promise,中间件 await 它,Koa 才知道要等。

追问 4async 函数里忘了写 await,会怎样?
参考回答

拿到的是一个 Promise 对象而不是查询结果,rows.length 是 undefined。而且这个 Promise 如果 reject,没人接住就是一个未处理的 rejection。

# Promise封装mysql模块

30 秒速记

  • 封装:new Promise 里 pool.getConnection → connection.query → 成功 resolve(rows)、失败 reject(err) → connection.release()
  • release 要放在 resolve / reject 之外、无论成败都执行;getConnection 失败时没有连接可还
  • 简单查询其实可以直接用 pool.query,它内部就是借连接、查询、还连接
  • 第二个参数 values 用来填 ? 占位符,防注入全靠它
  • 调用方 await query(sql, values),出错用 try/catch 接

Promise 封装就是在回调里调 resolve 和 reject,把「借连接、执行 SQL、还连接」包成一个能 await 的函数。 写这种封装最重要的是连接一定要还:不管查询成功还是失败,connection.release() 都要执行,不然连接池很快被借光,所有请求都卡在等连接上。另外这个函数其实就是 pool.query 加一层 Promise,所以更简洁的写法是 new Promise 里直接调 pool.query,或者 util.promisify。

理解「Promise封装mysql模块」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 event-loop 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1封装里如果把 release 写在 if (err) reject(err) 的 else 分支里,会怎样?
参考回答

查询失败的时候连接不会被还回去。出错几次之后池子就满了,后面的请求全卡在 getConnection 上,看起来像服务挂了。

追问 2getConnection 返回 err 的时候,要不要 release?
参考回答

不用也不能,这时 connection 是 undefined,调 release 会直接报 TypeError。直接 reject(err) 就行。

追问 3这个封装和直接用 pool.query 有什么区别?
参考回答

功能上一样,pool.query 内部就是借连接、查询、自动还。所以普通查询写成 pool.query(sql, values, (err, rows) => ...) 包 Promise 更短,也不会忘记 release。

追问 4要在这个 query 上实现事务,能直接调三次 query('BEGIN')、query(...)、query('COMMIT') 吗?
参考回答

不行,每次 query 都可能借到不同的连接,BEGIN 和 COMMIT 不在同一个连接上,事务是失效的。事务要单独封装一个函数,手动借一条连接,在它上面执行全部语句。

追问 5查询偶尔超时一直不返回,await 永远挂着,怎么办?
参考回答

给查询加超时:mysql 包支持 query({ sql, timeout: 5000 }, values, cb),超时会以 PROTOCOL_SEQUENCE_TIMEOUT 报错。连接池再配上 acquireTimeout,借不到连接也别无限等。

# Promise封装 ./async-db

← 8.1 mysql模块8.3 项目建表初始化 →

fe