Как обернуть асинхронные вызовы функций в функцию синхронизации в узле.js или Javascript?
Предположим, вы поддерживаете библиотеку, которая предоставляет функцию getData
. Ваши пользователи называют его, чтобы получить фактические данные:var output = getData();
Под капотом данные сохраняются в файле, чтобы вы реализовали getData
через узел.Яш встроенный fs.readFileSync
. Это очевидно как getData
и fs.readFileSync
функции синхронизации. Однажды вам сказали переключить базовый источник данных на репо, такое как MongoDB, к которому можно получить доступ только асинхронно. Вам также сказали, чтобы вы не разозлили своих пользователей,getData
API невозможно изменить, чтобы вернуть только обещание или потребовать параметр обратного вызова. Как вы соответствуете обоим требованиям?
асинхронная функция, использующая обратный вызов / обещание, является ДНК JavasSript и узла.js. Любое нетривиальное приложение JS, вероятно, пронизано этим стилем кодирования. Но эта практика может легко привести к так называемой пирамиде обратного вызова обреченности. Еще хуже, если какой-либо код в любом вызывающем абоненте в цепочке вызовов зависит от результата асинхронной функции, этот код должен быть обернут в обратный вызов функция также, накладывая ограничение стиля кодирования на вызывающего абонента. Время от времени я нахожу необходимость инкапсулировать асинхронную функцию (часто предоставляемую в сторонней библиотеке) в функцию синхронизации, чтобы избежать массового глобального повторного факторинга. Поиск решения по этому вопросу обычно заканчивался Узел Волокна или пакеты 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)
если функция 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 и ищет код для получения ожидаемого результата. Я приму решение, которое работает только в узле.js таким образом, вы можете потребовать любой пакет npm, несмотря на то, что он не работает в JSFiddle.
10 ответов:
deasync превращает асинхронную функцию в синхронизацию, реализованную с помощью механизма блокировки путем вызова узла.цикл событий 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); } }); }); }
Если функция Fiber действительно превращает асинхронную функцию сна в синхронизацию
Да. Внутри волокна функция ожидает перед регистрацией
ok
. Волокна не делают асинхронные функции синхронными, но позволяют писать синхронный код, который использует асинхронные функции, а затем будет работать асинхронно внутриFiber
.время от времени я нахожу необходимость инкапсулировать асинхронную функцию в функцию синхронизации, чтобы избежать массивного глобального ре-факторинг.
вы не можете. Невозможно сделать асинхронный код синхронным. Вам нужно будет предвидеть это в своем глобальном коде и писать его в асинхронном стиле с самого начала. Оберните ли вы глобальный код в волокно, используйте обещания, генераторы обещаний или простые обратные вызовы, зависит от ваших предпочтений.
моя цель состоит в том, чтобы свести к минимуму влияние на вызывающего абонента, когда метод сбора данных изменяется с синхронизации на асинхронный
как обещания, так и волокна могут это сделать.
вы не должны смотреть на то, что происходит вокруг вызов, который создает волокно, а скорее на то, что происходит внутри волокна. После того, как вы находитесь внутри волокна вы можете запрограммировать в стиле синхронизации. Например:
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() и defer () мы вызываем все методы с помощью 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 () и отложить()
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
илиPromise
s внутренне, тогда он избежит контекста волокна. Это можно обойти, издеваясьsetTimeout
,Promise.then
и все обработчики событий.так вот как вы можете получить волокно до
Promise
устранена. Этот код принимает асинхронную (возвращающую обещание) функцию и возобновляет волокно, когда обещание решено:основа-запись.js
console.log(require("./my-plugin").run());
async-lib.js
exports.getValueAsync = () => { return new Promise(resolve => { setTimeout(() => { resolve("Async Value"); }, 100); }); };
мой плагин.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 не является асинхронным, несмотря на вызов асинхронной функции.
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 с использованием асинхронной строки чтения.вопрос:
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
я боролся с этим сначала с узлом.JS и асинхронная.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 для узел.jshttpGet(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 });
EDIT: это, безусловно, мой самый спорный ответ, node теперь имеет ключевое слово yield, которое позволяет вам обрабатывать асинхронный код, как если бы он был синхронным. http://blog.alexmaccaw.com/how-yield-will-transform-node