如何将asynchronous函数调用包装到Node.js或Javascript中的同步函数中?

假设你维护一个公开函数getData的库。 您的用户称之为获取实际数据:
var output = getData();
数据保存在文件中,所以你使用Node.js内置的fs.readFileSync来实现getData 。 很显然, getDatafs.readFileSync都是同步函数。 有一天,您被告知将底层数据源切换到一个只能asynchronous访问的回购库(如MongoDB)。 你也被告知要避免让用户streamgetDatagetData API不能改变只返回一个promise或者需要一个callback参数。 你如何满足这两个要求?

使用callback / promise的asynchronous函数是JavasSript和Node.js的DNA。 任何不平凡的JS应用程序可能渗透了这种编码风格。 但这种做法很容易导致所谓的厄运callback金字塔。 更糟糕的是,如果调用链中的任何调用者的代码都依赖于asynchronous函数的结果,那么这些代码也必须被包装在callback函数中,并且对调用者施加编码风格约束。 有时我会发现需要将asynchronous函数(通常在第三方库中提供)封装到同步函数中,以避免大规模的全局重构。 在这个主题上寻找解决scheme通常最终会得到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真的把asynchronous函数hibernate成同步,输出应该是:

 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中创build了另一个简单的例子,并寻找代码来获得预期的输出。 我将接受一个只能在Node.js中使用的解决scheme,所以你可以自由地要求任何npm包,尽pipe不在JSFiddle中工作。

deasync将asynchronous函数变成同步,通过调用JavaScript层的Node.js事件循环来实现阻塞机制。 结果,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的合着者,模块是在发布这个问题后创build的,没有find可行的scheme。)

如果function光纤真的把asynchronousfunctionhibernate成同步

是。 在光纤内部,function在logging成功之前等待。 光纤不会使asynchronous函数同步,但允许编写使用asynchronous函数的同步代码,然后在Fiber内部asynchronous运行。

有时我会发现需要将asynchronous函数封装到同步函数中,以避免大规模的全局重构。

你不能。 使asynchronous代码同步是不可能的。 您将需要在全局代码中预测,并从一开始就以asynchronous方式编写它。 无论是将全局代码封装在光纤中,使用promise,promise生成器还是简单的callback都取决于您的偏好。

我的目标是当数据采集方法从同步更改为asynchronous时,最大限度地减less对调用者的影响

承诺和纤维都可以做到这一点。

还有一个npm同步模块。 用于同步执行查询的过程。

当你想以同步的方式运行并行查询时,节点限制就这么做,因为它永远不会等待响应。 同步模块对于这种解决scheme来说是非常完美的。

示例代码

 /*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

使Node.js代码同步在数据库等方面很重要。 但Node.js的实际优势在于asynchronous代码。 因为它是单线程无阻塞的。

我们可以使用重要的function来同步它Fiber()使用await()和defer()我们使用await()调用所有的方法。 然后用defer()replacecallback函数。

正常的asynchronous代码。这使用CallBackfunction。

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

使用Fibre()同步上面的代码,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())); }); 

我希望这会有所帮助。 谢谢

我无法find无法使用节点光纤解决的情况。 您使用节点光纤提供的示例的行为如预期。 关键是在光纤内部运行所有相关的代码,所以你不必在随机位置开始一个新的光纤。

让我们看一个例子:假设你使用了一些框架,这是你的应用程序的入口点(你不能修改这个框架)。 该框架将nodejs模块作为插件加载,并调用插件上的一些方法。 可以说这个框架只接受同步function,并不使用光纤本身。

有一个你想在你的插件中使用的库,但是这个库是asynchronous的,你也不想修改它。

当没有光纤运行时,主线程不能运行,但你仍然可以使用光纤创build插件! 只需创build一个包装器条目,启动光纤内的整个框架,以便从插件中获得执行结果。

下行:如果框架在内部使用setTimeoutPromise ,那么它将逃离光纤上下文。 这可以通过Promise.then setTimeoutPromise.then和所有事件处理程序来解决。

所以这是如何才能产生纤维,直到一个Promise解决。 这个代码需要一个asynchronous(Promise返回)函数,并在promise被parsing时恢复光纤:

框架-entry.js

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

asynchronouslib.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它按预期工作。

你不应该看看创build光纤的呼叫周围发生了什么,而是看光纤发生了什么。 一旦进入光纤,您可以同步编程。 例如:

函数f1(){
     console.log('wait ...'+ new Date);
    睡眠(1000);
     console.log('ok ...'+ new Date);   
 }

函数f2(){
     F1();
     F1();
 }

光纤(function(){
     F2();
 })。跑();

在光纤内部,您可以调用f1f2 ,就像同步一样sleep

在典型的Web应用程序中,您将在HTTP请求分派器中创build光纤。 完成之后,即使调用asynchronous函数(fs,数据库等),也可以以同步方式编写所有请求处理逻辑。

如今,这种生成器模式可以成为许多情况下的解决scheme。

这里是一个使用asynchronousreadline.question函数在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 

我首先用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(); }); } 

这个scheme将总是产生以下…

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

Javascript是一个单线程语言,你不想阻止你的整个服务器! asynchronous代码通过明确依赖关系消除了竞争条件。

学会喜欢asynchronous代码!

看看asynchronous代码的promises ,而不会创build一个callback地狱的金字塔。 我推荐node.js的promiseQ库

 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关键字,它允许您将asynchronous代码,如果它是同步的。 http://blog.alexmaccaw.com/how-yield-will-transform-node