源码阅读:promiseify
源码阅读:promiseify
简介
在 JavaScript 中,回调函数是一种常见的处理异步操作的方式。然而,使用回调函数可能会导致代码嵌套过深,难以理解和维护。Promiseify
解决了这个问题,它可以将基于回调的异步函数转换为返回Promise
的函数,让开发者更方便地处理异步操作并使用Promise
链式调用的方式编写代码,使用更加清晰和简洁的代码来处理异步操作。
Promiseify
的使用非常简单,只需要调用它的函数并传入需要转换的异步函数作为参数即可。Promiseify
会返回一个新的函数,这个新的函数返回一个Promise
对象。我们可以通过调用这个返回的函数来执行原始的异步操作,并使用Promise
链式调用的方式处理结果和错误。
promiseify
的基本用法如下:
- 引入
promiseify
模块(CommonJS 为例):
const promiseify = require('promiseify');
- 将需要转换的函数传入
promiseify
函数,并得到返回的Promise
版本的函数:
const promiseFunc = promiseify(callbackFunc);
- 使用返回的
promiseFunc
函数进行异步操作:
promiseFunc(args)
.then((result) => {
// 处理成功的结果
})
.catch((error) => {
// 处理错误
});
promiseify
的工作原理是通过将原始的回调函数包装在一个新的Promise
中,并根据回调函数的执行结果来决定Promise
的状态。如果回调函数执行成功,则Promise
会被解析为成功状态,并传递结果值;如果回调函数执行失败,则Promise
会被拒绝,并传递错误对象。
源码解读
'use strict';
首先,代码使用严格模式('use strict'
)来确保代码的严谨性和安全性。
接下来,定义了一个promiseify
函数,它接受两个参数:method
和ctx
(可选)。method
参数是需要转换的函数,ctx
参数是作为该函数的上下文(this
)。
/**
* promiseify
* @param {function} method function
* @param {object} ctx optional ctx for method
*/
function promiseify(method, ctx) {
// check first
if (typeof method !== 'function') {
throw new TypeError(String(method) + ' is not a function');
}
return function() {
// runtime args
var args = [].slice.call(arguments);
// runtime this
ctx = ctx || this;
return new Promise(function(resolve, reject) {
args.push(function(err) {
if (err) {
return reject(err);
}
var arg = [].slice.call(arguments);
if (arg.length === 2) {
resolve.call(this, arg[1]);
} else {
resolve.call(this, arg.slice(1));
}
});
try {
method.apply(ctx, args);
} catch (err) {
reject(err);
}
});
};
}
- 代码首先进行了参数检查,确保method参数是一个函数,如果不是函数,则抛出一个类型错误。
- 然后,返回一个新的函数,这个函数将成为返回的
Promise
版本的函数。 - 在新的函数内部,首先获取运行时的参数(
arguments
)并将其转换为一个数组。 - 接着,根据传入的上下文参数或默认的上下文(
this
),设置函数的执行上下文。 - 然后,创建一个新的
Promise
,在Promise
的构造函数中,将一个新的回调函数作为参数传入。该回调函数接受两个参数:resolve
和reject
。 - 回调函数中,首先检查是否有错误参数(
err
),如果有错误,则使用reject
方法将Promise
拒绝,并传递错误对象。 - 如果没有错误,使用
[].slice.call(arguments)
将回调函数的参数转换为一个数组(arg
)。 - 接下来,根据参数的长度来决定调用
resolve
时传递的参数。如果参数长度为2
,说明有一个错误参数和一个结果参数,此时调用resolve
方法,并传递结果参数(arg[1]
);否则,调用resolve
方法,并传递结果参数的数组(arg.slice(1)
)。 - 最后,在
try-catch
块中执行原始的异步函数(method.apply(ctx, args)
),如果发生错误,则使用reject
方法将Promise
拒绝,并传递错误对象。
/**
* promiseify all
* @param {object} o the target object
* @return {object} same to target object
*
* @example
* var fs = promiseify.all(require('fs'));
* fs.readFileAsync('file.txt', 'utf8')
* .then(function(s){ console.log(s); });
*
* var Connection = require('mysql/lib/Connection');
* promiseify.all(Connection.prototype);
* // conn.connectAsync / conn.queryAsync / conn.endAsync available now
*/
promiseify.all = function(o) {
Object.keys(o)
.filter(function(m) {
return typeof o[m] === 'function';
})
.forEach(function(m) {
o[m + 'Async'] = promiseify(o[m]);
});
return o;
};
这部分是一个promiseify.all
函数,它接受一个对象作为参数,并遍历该对象的所有属性。对于属性值为函数的属性,将其转换为Promise
版本的函数,并将新的函数添加到对象中,属性名为原始函数名加上Async
后缀。
Object.defineProperty(promiseify, "__esModule", { value: true });
promiseify.default = promiseify;
module.exports = promiseify;
这段代码主要是用于导出promiseify
函数作为一个模块。首先,使用Object.defineProperty
方法给promiseify
对象添加一个名为"__esModule"
的属性,属性值为true
。这是为了指示该模块是一个 ES 模块。接着,将promiseify.default
属性设置为promiseify
函数本身。这样,在使用import
语法导入时,可以直接获取到promiseify
函数。最后,使用module.exports
将promiseify
函数导出为一个模块。这样,在使用require
语法导入时,可以获取到promiseify
函数。
拓展
在JavaScript中,类数组(array-like object)是指具有数组特征的对象,但不是真正的数组。它们具有类似于数组的长度属性和通过索引访问元素的能力。然而,类数组对象没有数组的原型方法和属性。
常见的类数组对象包括:
arguments
对象:在函数内部自动创建的对象,用于存储传递给函数的参数。- DOM元素列表(NodeList):由查询DOM元素返回的对象集合,例如通过
querySelectorAll
方法获取的结果。 - 字符串:可以通过索引访问字符串中的字符。
- 类似数组的对象:某些对象可能被设计成与数组类似,例如通过实现类似数组的迭代器接口。
将类数组对象转为真正的数组有多种方法:
- 使用
Array.from()
方法:Array.from()
方法可以将类数组对象或可迭代对象转换为一个新的数组。例如:var array = Array.from(arrayLike);
- 使用展开运算符(Spread Operator):展开运算符(...)可以将类数组对象展开为一个新的数组。例如:
var array = [...arrayLike];
- 使用
Array.prototype.slice.call()
方法:可以通过调用Array.prototype.slice
方法并传入类数组对象作为其上下文来将其转换为数组。例如:var array = Array.prototype.slice.call(arrayLike);
- 使用
Array.prototype.concat()
方法:可以通过调用Array.prototype.concat
方法并传入类数组对象作为参数来将其转换为数组。例如:var array = Array.prototype.concat.call([], arrayLike);
需要注意的是,以上方法都是创建一个新的数组,而不是直接修改原始的类数组对象。