Как сделать, чтобы функция могла использовать как callback, так и async?
Имею библиотеку, которая поддерживает callback.
Вот пример функции для нее:
import fs from "fs";
removeFile(req, file, cb) {
fs.unlink(file.path, cb);
};
По всему приложению использую async/await:
import {promises as fs} from "fs";
async removeFile(filePath) {
return await fs.unlink(filePath);
};
Можно ли из этих двух сделать одну универсальною, с целью лаконичности?
Вот моя попытка, идентичны ли они, не сломаю ли я библиотеку, что работает на callback?
import {promises as fs} from "fs";
import fsCb from "fs";
async removeFile(filePath, cb = false) {
if (cb) {
return fsCb.unlink(filePath, cb)
}
return await fs.unlink(filePath);
};
Ответы (2 шт):
Так как большинство функций с обратными вызовами ничего не возвращают, то вряд ли опциональный возврат Promise может что-то сломать, если и ваши функции с колбеками возвращали undefined.
Разве что return await обычно считается излишней работой: нет смысла дожидаться результата Promise, чтобы сразу опять облечь его в Promise (ведь любая асинхронная функция возвращает Promise). Достаточно будет return fs.unlink(filePath);. Тогда вам даже не нужно будет объявлять функцию как async (таким двойным функциям не обязательно быть async, если они не используют await: при отсутствии колбека, они могут просто возвращать new Promise(), если нет чего-то готового, возвращающего Promise).
Хотя, если подумать, есть один возможный способ что-то сломать: если раньше при отсутствии колбека возникала ошибка, а теперь её не будет — тогда пользователь, который будет работать со старым интерфейсом и случайно забудет колбек (но не будет ждать Promise), может не получить сообщения об ошибке и не сможет понять, что не так. Так что лучше, наверное, перестраховаться и выпустить новую версию библиотеки как semver major.
Немного добавлю к ответу уважаемого vsemozhebuty.
Если бы мне пришлось писать современную библиотеку unicornlib, которую нужно было бы расширить таким вот применением callback-ов то я, наверное, сделал бы сиблинг-библиотеку unicornlib-legacy и там заворачивал бы асинхронные вызовы в вызовы колбеков.
Если совсем нет выбора, то не вмешивался бы в бизнес-логику самой функции. Сколько разного кода нужно дописать для поддержки "cb | promise" можно посмотреть на примере функции pipeline из библиотеки sharp.
Думаю, я бы пошёл через декорирование:
const { promises: fs } = require('fs');
function cbDecorator(fn) {
return (...args) => {
try {
fn(...args);
} catch (err) {
console.error(err);
}
}
}
function fnDecorator(fn) {
return (...args) => {
let cb;
if (typeof args[args.length - 1] === 'function') {
cb = cbDecorator(args.pop());
}
return fn(...args).then((res) => {
if (cb) {
cb(null, res);
}
return res;
}).catch((err) => {
if (cb) {
cb(err);
}
return Promise.reject(err);
});
};
}
async function removeFile(filePath) {
return fs.unlink(filePath);
};
module.exports.removeFile = fnDecorator(removeFile);