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

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

Асинхронная функция, использующая обратный вызов / обещание, является основой JavasSript и Node.js. Любое нетривиальное приложение JS, вероятно, пронизано этим стилем кодирования. Но эта практика может легко привести к так называемой пирамиде обратного вызова судьбы. Еще хуже, если какой-либо код в любом вызывающем объекте в цепочке вызовов зависит от результата асинхронной функции, этот код также должен быть включен в функцию обратного вызова, накладывая ограничение на стиль кодирования для вызывающего. Время от времени я нахожу необходимость инкапсулировать асинхронную функцию (часто предоставляемую в сторонней библиотеке) в функцию синхронизации, чтобы избежать масштабного глобального ре-факторинга. Поиск решения по этой теме обычно заканчивался использованием Node Fibres или пакетов 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.

10 ответов

Решение

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, Модуль был создан после публикации этого вопроса и не нашел работоспособного предложения.)

Существует также модуль синхронизации 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

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

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 не является асинхронным, несмотря на вызов асинхронной функции.

РЕДАКТИРОВАТЬ: я понимаю, много раз вам нужно обернуть асинхронную функцию внутри функции синхронизации внутри контроллера. Для таких ситуаций вот трюк для вечеринки:

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, где я определил обратный вызов, и создайте событие, где я вызвал обратный вызов.

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

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

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

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

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

И обещания и волокна могут сделать это.

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

Вот пример последовательных консольных запросов в 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

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

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

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

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

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

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

рамочные-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 это работает как ожидалось.

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

function f1 () {
    console.log ('wait...' + новая дата);
    сон (1000);
    console.log('ok... ' + новая дата);   
}

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

Волокно (функция () {
    f2();
}).бежать();

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

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

Синхронизация кода Node.js необходима в нескольких аспектах, таких как база данных. Но реальное преимущество Node.js заключается в асинхронном коде. Так как это однониточный неблокирующий.

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

Обычный асинхронный код. Использует функции 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 (), await () и 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()));

});

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

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

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

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

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

Посмотри на promises для асинхронного кода без создания пирамиды ада обратного вызова. Я рекомендую библиотеку promiseQ для 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

РЕДАКТИРОВАТЬ: это, безусловно, мой самый противоречивый ответ, узел теперь имеет ключевое слово yield, которое позволяет обрабатывать асинхронный код, как если бы он был синхронным. http://blog.alexmaccaw.com/how-yield-will-transform-node

Другие вопросы по тегам