promisify化mysql模块的connection.query函数. 在学习mysql模块的时候需要大量使用connection.query, 在回调里拿查询结果的方式非常不优雅, node的原生util库提供了一个promisify方法将普通函数Promise化, 非常方便, 但发现有一个缺点, 只能拿到回调的第一二个参数, 于是尝试自己使用es6实现 promisify, 并优化这个地方
准备 - 创建mysql连接
// 创建连接
const connection = mysql.createConnection({
user: 'root',
password: 'password',
})
connection.connect()
回调写法
connection.query('show databases;', (err, results, fields) => {
if(err) {
console.error('Query Fail!')
throw err
} else {
console.log('Query Success!')
console.table(results)
console.log(fields)
}
})
成功拿到结果, OK 可以继续了
Query Success!
┌─────────┬──────────────────────┐
│ (index) │ Database │
├─────────┼──────────────────────┤
│ 0 │ 'information_schema' │
│ 1 │ 'mysql' │
│ 2 │ 'performance_schema' │
│ 3 │ 'sys' │
│ 4 │ 'test' │
└─────────┴──────────────────────┘
实现promisify
/**
* Promise化一个带回调的普通函数
* 回调函数的第一个参数必须是Error
* @param {Funciton} fn function to call
* @return 返回一个Promise的函数, 即Promise化
* 在返回的Promise里面处理fn的回调函数
*/
function promisify(fn) {
return function(...args) {
let _args = args.slice()// 拷贝一份参数, 原则上不应该修改传入参数的值
return new Promise( (resolve, reject) => {// 这里必须使用箭头函数, 为了获得真正的上下文
function cb(err, ...results) { // 处理回调结果
if (err) {
reject(err)
} else {
// 这里util.promisify只会传err之后的第一个参数, 相当于resolve(result[0])
// 给resolve传递了err之外的其他所有参数, 解决缺陷!
resolve(results)
}
}
_args.push(cb)
fn.apply(this, _args)
// fn.call(this, ..._args) // 也可以call
})
}
}
使用promisify
async function testPromisify() {
const queryAsync = promisify(connection.query)
const [results, fields] = await queryAsync('show databases;') // 抛出异常
console.log('Query Success!')
console.table(results)
console.log(fields)
}
发现抛出异常
[unhandledRejection] TypeError: Cannot read property 'typeCast' of undefined
at query (E:\mygit\exercise\node_modules\mysql\lib\Connection.js:201:34)
at Promise (E:\mygit\exercise\server\promisify.js:22:10)
at new Promise (<anonymous>)
at E:\mygit\exercise\server\promisify.js:13:12
at testPromisify (E:\mygit\exercise\server\promisify.js:48:35)
at Object.<anonymous> (E:\mygit\exercise\server\promisify.js:72:1)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
查看mysql源码 E:\mygit\exercise\node_modules\mysql\lib\Connection.js:201:34, 发现原来是上下文不对
Connection.prototype.query = function query(sql, values, cb) {
var query = Connection.createQuery(sql, values, cb);
query._connection = this;
console.log('this === global', this === global); // true
console.log('this.config', this.config); // undefined
if (!(typeof sql === 'object' && 'typeCast' in sql)) {
query.typeCast = this.config.typeCast; // 这里的this, 打印了一下发现指向global
}
// ...
};
为什么会这样呢? 因为... query函数Promise化后失去了宿主环境(context)
// query 的 this 指向 connection
connection.query('show databases;', (err, results, fields) => { ... });
// queryAsync 的 this 指向 global
const queryAsync = promisify(connection.query)
const [results, fields] = await queryAsync('show databases;')
解决方法很简单: 绑定context
const queryAsync = promisify(connection.query)
const [results, fields] = await queryAsync.call(connection, 'show databases;')
// or
connection.queryAsync = promisify(connection.query)
const [results, fields] = await connection.queryAsync('show databases;')
总结
最终代码为
function promisify(fn) {
return function(...args) {
let _args = args.slice()
return new Promise( (resolve, reject) => {
function cb(err, ...results) {
err ? reject(err) : resolve(results)
}
_args.push(cb)
fn.apply(this, _args)
})
}
}
const connection = mysql.createConnection({
user: 'root',
password: 'password',
})
connection.connect()
async function testPromisify() {
connection.queryAsync = promisify(connection.query)
const [results, fields] = await connection.queryAsync('show databases;')
console.log('Query Success!')
console.table(results)
console.log(fields)
}
// 因为 await并未处理异常, 处理一下全局异常
process.on('uncaughtException', (err) => {
console.log('[uncaughtException]', err)
})
process.on('unhandledRejection', (err) => {
console.log('[unhandledRejection]', err)
})
非常好, 满意!