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

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

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

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

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

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 и ищет код для получения ожидаемого результата. Я приму решение, которое работает только в узел.js поэтому вы можете потребовать любой пакет npm, несмотря на то, что он не работает в JSFiddle.

10 ответов


deasync превращает асинхронную функцию в синхронизацию, реализованную с помощью механизма блокировки путем вызова узла.цикл событий js на уровне JavaScript. В результате, deasync только блокирует последующий запуск кода, не блокируя всю ветку, ни incuring напряженного ожидания. С помощью этого модуля, вот ответ на вызов 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)

(Disclaimer: я соавтор 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


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

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

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

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

моя цель-минимизировать влияние на вызывающего абонента при изменении метода сбора данных с синхронизации на async

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


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

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 как будто они были синхронизацией.

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


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

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

обычный асинхронный код.Это использует функции обратного вызова.

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-fibers. Пример, приведенный с помощью node-fibers, ведет себя так, как ожидалось. Ключ должен запустить весь соответствующий код внутри волокна, поэтому вам не нужно запускать новое волокно в случайных положениях.

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

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

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

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

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

основа-запись.js

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

async-lib.js

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

my-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());
};

мои записи.js

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

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


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

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

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


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

вот пример последовательных консольных приглашений в nodejs с использованием async readline.вопрос:

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

сначала я боролся с этим с node.JS и асинхронная.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

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

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

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