Во-первых, даже старый код может использовать обещания просто отлично. Обещания могут быть легко заполнены в любой версии node.js или браузере, и вызывающий абонент может использовать .then()
и .catch()
просто отлично. Итак, мой первый аргумент будет то, что пришло время двигаться вперед и оставить прежний стиль обратного вызова. Использование обещаний никому не мешает использовать ваш код, оно просто заставляет их перенести свои знания в области программирования в правильное десятилетие.
Если вы действительно хотите предложить как обратный вызов, так и вариант обещания в одном интерфейсе, тогда Вы не можете просто поместить async
в функцию и заставить все работать. Это не то, что делает async
. У него нет такого уровня сверхдержавы. Кроме того, если вам нужна совместимость со старой версией node.js, то эта версия также может не поддерживать async
.
Обычный способ создания чего-либо, что может быть вызвано в любом случае, - это определить, есть ли обратный вызов. передается, и код адаптируется в зависимости от того, был ли он передан, возвращая обещание, подключенное к завершению вашего кода, если обратный вызов не был передан, или используя обратный вызов, если он был передан.
Итак, если бы у вас был интерфейс 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), чтобы сделать обещанную версию интерфейса обратного вызова автоматически для вас. Если вы хотите поддерживать версии node.js, достаточно старые, чтобы util.promisify()
даже не существовало, то их также можно встроить вручную в несколько строк кода.
По соображениям эффективности он кэширует обещанное версия функции при первом использовании в качестве свойства ._p
в вызываемой функции, поэтому при последующих вызовах она может использовать точно такую же многообещающую версию функции.
Обратите внимание, я бы предпочел дизайн, оптимизированный первым для интерфейса обещания, потому что это более вероятное использование в будущем, так как это будущее языка Javascript и должно использоваться чаще. Но для такой функции вы бы хотели использовать интерфейс fs.promises
к модулю fs
, который предполагает, по крайней мере, узел v10.0.
Если вы можете предположить, что в последнем случае узел v10, то с интерфейсом fs.promises
это становится немного проще, и он более оптимизирован для использования в обещаниях:
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);
}
}