Как переносить вызовы функции async в функцию синхронизации в Node.js или Javascript?

Предположим, что вы поддерживаете библиотеку, которая предоставляет функцию getData. Ваши пользователи назовут его для получения фактических данных:
var output = getData();
Под капотом данные сохраняются в файле, поэтому вы внедрили getData с помощью Node.js встроенного fs.readFileSync. Очевидно, что функции getData и fs.readFileSync являются функциями синхронизации. Однажды вам сказали переключить базовый источник данных на репо, например MongoDB, к которому можно получить доступ только асинхронно. Вам также сказали не беспокоить своих пользователей, getData API не может быть изменен, чтобы вернуть просто обещание или потребовать параметр обратного вызова. Как вы отвечаете обоим требованиям?

Асинхронная функция с использованием обратного вызова/обещания - это ДНК JavasSript и Node.js. Любое нетривиальное приложение JS, вероятно, пронизано этим стилем кодирования. Но эта практика может легко привести к так называемой обратной пирамиде гибели. Хуже того, если какой-либо код в любом вызывающем абоненте в цепочке вызовов зависит от результата функции async, этот код также должен быть включен в функцию обратного вызова, накладывая ограничение стиля кодирования на вызывающего. Время от времени я нахожу необходимость инкапсулировать функцию асинхронизации (часто предоставляемую в сторонней библиотеке) в функцию синхронизации, чтобы избежать массового глобального повторного факторинга. Поиск решения по этому вопросу обычно заканчивался Node Fibers или пакетами npm, полученными из него. Но Fibers просто не могут решить проблему, с которой я столкнулся. Даже пример, представленный автором Фибера, иллюстрирует недостаток:

...
Fiber(function() {
    console.log('wait... ' + new Date);
    sleep(1000);
    console.log('ok... ' + new Date);
}).run();
console.log('back in main');

Фактический выход:

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
back in main
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)

Если функция Fiber действительно переключает асинхронную функцию в режим синхронизации, выход должен быть:

wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
back in main

Я создал еще один простой пример в JSFiddle и ищет код для получения ожидаемого результата. Я приму решение, которое работает только в Node.js, поэтому вы можете требовать любой пакет npm, несмотря на то, что не работаете в JSFiddle.

Ответ 1

deasync превращает асинхронную функцию в синхронизацию, реализованную с помощью механизма блокировки, вызывая цикл Node.js событий на уровне JavaScript. В результате, deasync только блокирует последующий код от запуска без блокировки всей нити и не ожидает ожидание ожидания. С помощью этого модуля, вот ответ на вызов jsFiddle:

function AnticipatedSyncFunction(){
  var ret;
  setTimeout(function(){
      ret = "hello";
  },3000);
  while(ret === undefined) {
    require('deasync').runLoopOnce();
  }
  return ret;    
}


var output = AnticipatedSyncFunction();
//expected: output=hello (after waiting for 3 sec)
console.log("output="+output);
//actual: output=hello (after waiting for 3 sec)

(отказ от ответственности: я являюсь соавтором deasync. Модуль был создан после публикации этого вопроса и не нашел действующего предложения.)

Ответ 2

Также есть модуль синхронизации npm. который используется для синхронизации процесса выполнения запроса.

Если вы хотите запускать параллельные запросы синхронным образом, узел ограничивает это, потому что он никогда не ждет ответа. и модуль синхронизации идеально подходит для такого решения.

Образец кода

/*require sync module*/
var Sync = require('sync');
    app.get('/',function(req,res,next){
      story.find().exec(function(err,data){
        var sync_function_data = find_user.sync(null, {name: "sanjeev"});
          res.send({story:data,user:sync_function_data});
        });
    });


    /*****sync function defined here *******/
    function find_user(req_json, callback) {
        process.nextTick(function () {

            users.find(req_json,function (err,data)
            {
                if (!err) {
                    callback(null, data);
                } else {
                    callback(null, err);
                }
            });
        });
    }

ссылка ссылки: https://www.npmjs.com/package/sync

Ответ 3

Если функция Fiber действительно превращает функцию асинхронного режима в синхронизм

Да. Внутри волокна функция ждет до регистрации ok. Волокна не делают синхронные асинхронные функции, но позволяют писать синхронный код, который использует асинхронные функции, а затем будет выполняться асинхронно внутри Fiber.

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

Вы не можете. Синхронно сделать асинхронный код невозможно. Вам нужно будет предвидеть это в своем глобальном коде и записать его с асинхронным стилем с самого начала. Будете ли вы обменивать глобальный код на волокно, используйте promises, генераторы обещаний или простые обратные вызовы зависят от ваших предпочтений.

Моя цель состоит в том, чтобы минимизировать влияние на вызывающего абонента, когда метод сбора данных изменен с синхронизации на асинхронный

Оба promises и волокна могут это сделать.

Ответ 4

Выполнение синхронизации Node.js важно в нескольких аспектах, таких как база данных. Но фактическое преимущество Node.js заключается в асинхронном коде. Поскольку он неблокируется одним потоком.

мы можем синхронизировать его, используя важную функциональность Fiber() Используйте wait() и отложите() мы вызываем все методы, используя await(). затем замените функции обратного вызова на defer().

Нормальный Async-код. Это использует функции CallBack.

function add (var a, var b, function(err,res){
       console.log(res);
});

 function sub (var res2, var b, function(err,res1){
           console.log(res);
    });

 function div (var res2, var b, function(err,res3){
           console.log(res3);
    });

Синхронизируйте вышеуказанный код с помощью Fiber(), wait() и defer()

fiber(function(){
     var obj1 = await(function add(var a, var b,defer()));
     var obj2 = await(function sub(var obj1, var b, defer()));
     var obj3 = await(function sub(var obj2, var b, defer()));

});

Надеюсь, это поможет. Благодарю вас.

Ответ 5

Вы должны использовать обещания:

const asyncOperation = () => {
    return new Promise((resolve, reject) => {
        setTimeout(()=>{resolve("hi")}, 3000)
    })
}

const asyncFunction = async () => {
    return await asyncOperation();
}

const topDog = () => {
    asyncFunction().then((res) => {
        console.log(res);
    });
}

Мне больше нравятся определения функций стрелок. Но любая строка формы "() => {...}" также может быть записана как "function() {...}"

Поэтому topDog не async, несмотря на вызов функции async.

enter image description here

EDIT: Я понимаю, что много раз вам нужно, чтобы асинхронная функция внутри функции синхронизации находилась внутри контроллера. Для этих ситуаций здесь есть трюк:

const getDemSweetDataz = (req, res) => {
    (async () => {
        try{
            res.status(200).json(
                await asyncOperation()
            );
        }
        catch(e){
            res.status(500).json(serviceResponse); //or whatever
        }
    })() //So we defined and immediately called this async function.
}

Используя это с обратными вызовами, вы можете сделать обертку, которая не использует обещания:

const asyncOperation = () => {
    return new Promise((resolve, reject) => {
        setTimeout(()=>{resolve("hi")}, 3000)
    })
}

const asyncFunction = async (callback) => {
    let res = await asyncOperation();
    callback(res);
}

const topDog = () => {
    let callback = (res) => {
        console.log(res);
    };

    (async () => {
        await asyncFunction(callback)
    })()
}

Применяя этот трюк к EventEmitter, вы можете получить те же результаты. Определите прослушиватель EventEmitter, где я определил обратный вызов, и испустил событие, в котором я вызвал обратный вызов.

Ответ 6

Вы не должны смотреть на то, что происходит вокруг вызова, который создает волокно, а скорее на то, что происходит внутри волокна. Когда вы находитесь внутри волокна, вы можете запрограммировать стиль синхронизации. Например:

function f1() {
    console.log('wait... ' + new Date);
    sleep(1000);
    console.log('ok... ' + new Date);   
}

function f2() {
    f1();
    f1();
}

Fiber(function() {
    f2();
}).run();

Внутри волокна вы вызываете f1, f2 и sleep, как если бы они были синхронизированы.

В типичном веб-приложении вы создадите Fiber в своем диспетчере запросов HTTP. Как только вы это сделаете, вы можете написать всю логику обработки запросов в стиле синхронизации, даже если она вызывает асинхронные функции (fs, базы данных и т.д.).

Ответ 7

Я не могу найти сценарий, который невозможно решить с помощью node -fibers. Пример, который вы предоставили с помощью node -fibers, ведет себя так, как ожидалось. Ключом является запуск всего соответствующего кода внутри волокна, поэтому вам не нужно начинать новое волокно в случайных позициях.

Давайте посмотрим на пример: Предположим, вы используете некоторую фреймворк, которая является точкой входа вашего приложения (вы не можете изменить эту структуру). Эта структура загружает модули nodejs в виде плагинов и вызывает некоторые методы для плагинов. Допустим, что эта структура допускает только синхронные функции и сама не использует волокна.

Существует библиотека, которую вы хотите использовать в одном из ваших плагинов, но эта библиотека является асинхронной, и вы также не хотите ее изменять.

Основной поток не может быть получен, если ни одно волокно не работает, но вы все равно можете создавать плагины с помощью волокон! Просто создайте запись обертки, которая запустит всю структуру внутри волокна, чтобы вы могли выполнить выполнение из плагинов.

Даунсайд: если инфраструктура использует setTimeout или Promise внутренне, то она выйдет из контекста волокна. Это можно обойти, издеваясь над setTimeout, Promise.then и всеми обработчиками событий.

Таким образом, вы можете получить волокно до тех пор, пока не будет разрешено Promise. Этот код принимает функцию async (Promise return) и возобновляет волокно, когда обещание разрешено:

рамки-entry.js

console.log(require("./my-plugin").run());

<сильные > асинхронной-lib.js

exports.getValueAsync = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve("Async Value");
    }, 100);
  });
};

мой-plugin.js

const Fiber = require("fibers");

function fiberWaitFor(promiseOrValue) {
  var fiber = Fiber.current, error, value;
  Promise.resolve(promiseOrValue).then(v => {
    error = false;
    value = v;
    fiber.run();
  }, e => {
    error = true;
    value = e;
    fiber.run();
  });
  Fiber.yield();
  if (error) {
    throw value;
  } else {
    return value;
  }
}

const asyncLib = require("./async-lib");

exports.run = () => {
  return fiberWaitFor(asyncLib.getValueAsync());
};

мой-entry.js

require("fibers")(() => {
  require("./framework-entry");
}).run();

При запуске node framework-entry.js он выдает ошибку: Error: yield() called with no fiber running. Если вы запустите node my-entry.js, он работает как ожидалось.

Ответ 8

    const wrap = fn => async (...args) => {
        const res = await fn(...args)
        // do stuff
        return res
    }

Ответ 9

В настоящее время эта модель генератора может быть решением во многих ситуациях.

Вот пример последовательных консольных запросов в nodejs с использованием функции async readline.question:

var main = (function* () {

  // just import and initialize 'readline' in nodejs
  var r = require('readline')
  var rl = r.createInterface({input: process.stdin, output: process.stdout })

  // magic here, the callback is the iterator.next
  var answerA = yield rl.question('do you want this? ', r=>main.next(r))    

  // and again, in a sync fashion
  var answerB = yield rl.question('are you sure? ', r=>main.next(r))        

  // readline boilerplate
  rl.close()

  console.log(answerA, answerB)

})()  // <-- executed: iterator created from generator
main.next()     // kick off the iterator, 
                // runs until the first 'yield', including rightmost code
                // and waits until another main.next() happens

Ответ 10

Сначала я боролся с этим node.js, а async.js - лучшая библиотека, которую я нашел, чтобы помочь вам справиться с этим. Если вы хотите написать синхронный код с node, подход будет таким образом.

var async = require('async');

console.log('in main');

doABunchOfThings(function() {
  console.log('back in main');
});

function doABunchOfThings(fnCallback) {
  async.series([
    function(callback) {
      console.log('step 1');
      callback();
    },
    function(callback) {
      setTimeout(callback, 1000);
    },
    function(callback) {
      console.log('step 2');
      callback();
    },
    function(callback) {
      setTimeout(callback, 2000);
    },
    function(callback) {
      console.log('step 3');
      callback();
    },
  ], function(err, results) {
    console.log('done with things');
    fnCallback();
  });
}

эта программа будет ВСЕГДА производить следующее...

in main
step 1
step 2
step 3
done with things
back in main

Ответ 11

Javascript - это однопоточный язык, вы не хотите блокировать весь сервер! Асинхронный код устраняет условия гонки, делая явно явные зависимости.

Научитесь любить асинхронный код!

Взгляните на promises для асинхронного кода, не создавая пирамиду обратного ада. Я рекомендую библиотеку promQ для node.js

httpGet(url.parse("http://example.org/")).then(function (res) {
    console.log(res.statusCode);  // maybe 302
    return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
    console.log(res.statusCode);  // maybe 200
});

http://howtonode.org/promises

EDIT: это, безусловно, самый спорный ответ, node теперь имеет ключевое слово yield, которое позволяет обрабатывать асинхронный код, как если бы он был синхронным. http://blog.alexmaccaw.com/how-yield-will-transform-node