node.js может ли метод быть написан так, чтобы его можно было вызывать обоими способами - то есть с помощью callback или async / await? - PullRequest
0 голосов
/ 31 марта 2020

За эти годы я создал библиотеку стандартных методов, которые я использую в своих проектах. Многие из них имеют обратные вызовы. Недавно я узнал об обещаниях и async / await, чтобы сделать чтение кода синхронным и похожим на более ранние обратные вызовы.

Для текущих и будущих проектов я хотел бы использовать async / await для моих старых методов в библиотеке. Можно ли просто добавить «асин c» перед ними, а затем, когда я звоню им, я добавляю «жду», чтобы сделать их обещание дружелюбным. Тем не менее, в то же время мои старые проекты могут использовать их и называть их обратными вызовами?

, например:

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);


    });

};

, для этого метода я могу просто добавить «asyn c» следующим образом :

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

, а затем мои старые проекты называют его обратными вызовами:

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

, а новые проекты называют его:

let jsonResponse = await checkIfFileExists(fileToCheck);

Ответы [ 2 ]

2 голосов
/ 31 марта 2020

Существуют различные библиотеки для «обещания» путем обертывания существующих функций, которые используют стандартные параметры обратного вызова таким образом, чтобы возвращать обещания. Это может быть полезно для вас.

Например: https://www.npmjs.com/package/es6-promisify

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

const exists = await checkIfFileExistsPromise(...);

Также обратите внимание, что fs имеет API на основе обещаний, встроенный прямо в Node.js в эти дни: https://nodejs.org/api/fs.html#fs_fs_promises_api

1 голос
/ 31 марта 2020

Во-первых, даже старый код может использовать обещания просто отлично. Обещания могут быть легко заполнены в любой версии 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);
    }
}
...