import initialParams from './internal/initialParams';
import setImmediate from './internal/setImmediate';
import { isAsync } from './internal/wrapAsync'
/**
* 将同步函数转为异步,将返回值传给 callback。
* 帮助同步函数传入瀑布函数(waterfall)、串行函数(series)或其他 async 函数。
* 除了最后一个 callback 参数外,所有传入的参数都会传给包装的函数。
* 抛出的错误则会传给 callback。
*
* 如果传给 `asyncify` 的函数返回了一个 Promise,则回调函数会自动根据状态调用 resolved/rejected,
* 而不是仅仅返回同步的值。
*
* 这样就意味着支持 ES2017 `async` 函数。
*
* @name asyncify
* @static
* @memberOf module:Utils
* @method
* @alias wrapSync
* @category Util
* @param {Function} func - 待转换成 {@link AsyncFunction} 的同步函数、返回 Promise 的函数。
* @returns {AsyncFunction} `func` 的异步包装,
* 会以 `(args..., callback)` 调用。
* @example
*
* // 传入常规同步函数
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(JSON.parse),
* function (data, next) {
* // data 是解析后的 text
* // 若出现错误,会被捕获。
* }
* ], callback);
*
* // 传入返回 promise 的函数
* async.waterfall([
* async.apply(fs.readFile, filename, "utf8"),
* async.asyncify(function (contents) {
* return db.model.create(contents);
* }),
* function (model, next) {
* // `model` 是 model 对象实例
* // 若出现错误,此函数会被跳过
* }
* ], callback);
*
* // es2017 例子,当然如果 JS 环境支持 async 函数,就不需要 `asyncify` 化
* var q = async.queue(async.asyncify(async function(file) {
* var intermediateStep = await processFile(file);
* return await somePromise(intermediateStep)
* }));
*
* q.push(files);
*/
export default function asyncify(func) {
if (isAsync(func)) {
return function (...args/*, callback*/) {
const callback = args.pop()
const promise = func.apply(this, args)
return handlePromise(promise, callback)
}
}
return initialParams(function (args, callback) {
var result;
try {
result = func.apply(this, args);
} catch (e) {
return callback(e);
}
// if result is Promise object
if (result && typeof result.then === 'function') {
return handlePromise(result, callback)
} else {
callback(null, result);
}
});
}
function handlePromise(promise, callback) {
return promise.then(value => {
invokeCallback(callback, null, value);
}, err => {
invokeCallback(callback, err && err.message ? err : new Error(err));
});
}
function invokeCallback(callback, error, value) {
try {
callback(error, value);
} catch (err) {
setImmediate(e => { throw e }, err);
}
}