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

abb*_*bbr 114 javascript asynchronous synchronous node.js node-fibers

假设您维护一个公开函数的库getData.您的用户将其调用以获取实际数据:
var output = getData();
在引擎盖下,数据保存在文件中,因此您getData使用内置的Node.js 实现fs.readFileSync.很明显这两个getDatafs.readFileSync是同步的功能.有一天,您被告知要将基础数据源切换到MongoDB等只能异步访问的仓库.您还被告知要避免惹恼您的用户,getData不能更改API以仅返回承诺或要求回调参数.你如何满足这两个要求?

使用回调/保证的异步函数是JavasSript和Node.js的DNA.任何非平凡的JS应用程序都可能充满了这种编码风格.但这种做法很容易导致所谓的厄运金字塔回调.更糟糕的是,如果调用链中任何调用者中的任何代码都依赖于异步函数的结果,那么这些代码也必须包含在回调函数中,对调用者施加编码样式约束.我不时发现需要将异步功能(通常在第三方库中提供)封装到同步功能中,以避免大规模的全局重新分解.搜索关于此主题的解决方案通常最终得到Node Fibers或从中派生的npm包.但纤维无法解决我所面临的问题.甚至纤维作者提供的例子说明了这一缺陷:

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

实际产量:

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)
Run Code Online (Sandbox Code Playgroud)

如果函数Fiber确实将异步函数sleep转为同步,则输出应为:

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
Run Code Online (Sandbox Code Playgroud)

我在JSFiddle中创建了另一个简单的例子,并寻找产生预期输出的代码.我会接受一个只适用于Node.js的解决方案,所以你可以自由地要求任何npm包,尽管不能在JSFiddle中工作.

abb*_*bbr 101

deasync将异步函数转换为同步,通过在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)
Run Code Online (Sandbox Code Playgroud)

(免责声明:我是该作者的共同作者deasync.该模块是在发布此问题后创建的,但未找到可行的提案.)

  • 到目前为止,github问题跟踪器中记录了一个已确认的问题.问题已在Node v0.12中修复.我所知道的其余部分仅仅是毫无根据的推测,不值得记录.如果您认为您的问题是由deasync引起的,请发布一个独立的,可复制的方案,我会调查. (5认同)
  • 我无法让它正常工作.如果您希望更多地使用它,您应该改进该模块的文档.我怀疑作者确切知道使用该模块的后果是什么,如果他们这样做,他们肯定不会记录它们. (3认同)

小智 5

还有一个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);
                }
            });
        });
    }
Run Code Online (Sandbox Code Playgroud)

参考链接:https://www.npmjs.com/package/sync


use*_*309 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);
    });
}
Run Code Online (Sandbox Code Playgroud)

我更喜欢箭头函数定义。但是任何形式为“() => {...}”的字符串也可以写成“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.
}
Run Code Online (Sandbox Code Playgroud)

将此与回调一起使用,您可以进行不使用承诺的包装:

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)
    })()
}
Run Code Online (Sandbox Code Playgroud)

通过将此技巧应用于 EventEmitter,您可以获得相同的结果。在我定义回调的地方定义 EventEmitter 的侦听器,并在我调用回调的地方发出事件。