node.js是否可以编写一个方法,使其可以双向调用,即使用回调或async/await



多年来,我创建了一个在项目中使用的标准方法库。他们中的许多人都有回拨电话。我最近学习了promise和async/await,使代码读取同步,并在早期的回调中喜欢它。

对于正在进行的和未来的项目,我想使用async/await来处理库中的旧方法。是否可以简单地在它们前面添加"async",然后当我调用它们时,添加"wait"以使它们承诺友好。但与此同时,我的旧项目可以使用它们并将其称为回调?

例如:

var checkIfFileExists = function(filename,callback){
fs.stat(filename, function(err, stat) {
if(err == null) {
let jsonObj =   {   'status'        : true,
'error'         : false
};
} else if(err.code == 'ENOENT') {
console.log( 'File ' + filename + ' does not exists');
let jsonObj = { 'status'        : false,
'error'     :   err
};
} else {
let jsonObj =   { 'status'        : false,
'error'     : err
};
}
callback(jsonObj);

});
};

对于这种方法,我可以简单地添加"async",如下所示:

var checkIfFileExists = async function(filename,callback){ .... }

然后我的老项目称之为回调:

checkIfFileExists(fileToCheck, function(jsonResponse){
// do something with response
});

新项目称之为:

let jsonResponse = await checkIfFileExists(fileToCheck);

有各种库可以通过基本上包装使用标准回调参数的现有函数来"promising",从而返回promise。这可能对你有用。

例如:https://www.npmjs.com/package/es6-promisify

const {promisify} = require("es6-promisify");
const checkIfFileExistsPromise = promisify(checkFileIfExists);
const exists = await checkIfFileExistsPromise(...);

还要注意的是,fs已经在Node.js中内置了一个基于promise-based的API:https://nodejs.org/api/fs.html#fs_fs_promises_api

首先,即使是较旧的代码也可以很好地使用promise。Promise可以很容易地在几乎任何版本的node.js或浏览器中进行polyfilled,调用方可以很好地使用.then().catch()。所以,我的第一个论点是,现在是时候继续前进,放弃旧的回调风格了。使用promise并不能阻止任何人使用您的代码,它只是迫使他们将编程知识转移到正确的十年中。

如果您确实想在同一个接口中同时提供回调和promise选项,那么,您不能只将async放在函数上,然后让一切正常工作。async不是这么做的。它没有那种超能力。此外,如果您希望与旧版本的node.js兼容,那么该版本可能也不支持async

制作可以用任何一种方式调用的东西的通常方法是检测回调是否通过,代码根据是否通过进行调整,如果没有通过回调,则返回与代码完成挂钩的promise,如果通过了回调,则使用回调。

所以,如果你有一个接口checkIfFileExists(...),你可以这样使用它:

// with callback
checkIfFileExists("myfile.txt", function(err, exists) {
if (err) {
console.log(err);
} else {
console.log(exists);
}
});
// with promise
checkIfFileExists("myfile.txt").then(function(exists) {
console.log(exists);
}).catch(function(err) {
console.log(err);
});

这里有一个实现:

const fs = require('fs');
const promisify = require('util').promisify;
function makePromisify(fn) {
if (fn._p) {
return fn._p;
} else {
fn._p = promisify(fn);
return fn._p;
}
}
// if not called with callback, then returns a promise
function checkIfFileExists(filename, callback) {
if (!callback) {
return makePromisify(checkIfFileExists)(filename);
} else {
fs.stat(filename, function(err, stats) {
if (err) {
if (err.code === 'ENOENT') {
callback(null, false);
} else {
callback(err);
}
} else {
callback(null, true);
}
});
}
}

此实现使用util.promisify()(添加在节点v8.0中(为您自动生成回调接口的承诺版本。如果您想支持旧到util.promisify()根本不存在的node.js版本,那么也可以在几行代码中手动构建。

出于效率原因,它在第一次用作被调用函数的._p属性时缓存函数的承诺版本,因此在随后的调用中,它可以使用完全相同的函数承诺版本。

注意,我更喜欢首先针对promise接口进行优化的设计,因为这是未来更可能的用途,因为这将是Javascript语言的未来,应该是更常见的用途。但是,对于这样的函数,您希望使用fs模块的fs.promises接口,并且该接口至少假定节点v10.0。


如果您可以假设在最新的节点v10,那么使用fs.promises接口会变得更简单,并且对于promise用法来说会更精简:

const fsp = require('fs').promises;
// if not called with callback, then returns a promise
function checkIfFileExists(filename, callback) {
async function _checkIfFilesExists(filename) {
try {
await fsp.stat(filename);
return true;
} catch(err) {
if (err.code === 'ENOENT') {
return false;
} else {
throw err;
}
}
}
if (!callback) {
return _checkIfFileExists(filename);
} else {
_checkIfFileExists(filename).then(result => {
callback(null, result);
}).catch(callback);
}
}

最新更新