新人求问,异步如何转同步,还是我的思路不对?
发布于 4天前 作者 timerainv7 337 次浏览 来自 问答

才开始学node没多久,遇到了好多问题,感觉最让我困扰的就是异步转同步

  1. 有一次遇到的问题大概代码是这样的: for(var i=arr.length; i>0; i–){ fun(); if(i==0){ console.log(“ok”) } } 但是我发现每次fun()还没执行完就先输出“OK”。我知道大概是fun()被放进线程池没有立即执行所以要后执行,但是我不想这样。。
  2. 还有一次在使用sqlLite模块时遇到的,大概是这样: var db = new sqlite3.Database(‘xxx’); … for(var i=0; i<arr.length; i++){ … db.all(sql1, function(){ … console.log( arr[i] ); db.all(sql2, function(){ … console.log( arr[i] ); }) }) } 具体忘记了,但是问题我记得,i的值可以传递到第一层的db.all中,但是传递不到第二层的db.all中,导致arr[i]值是undefined。
  3. 还有就是写代码的时候不知不觉就嵌套了很多层。。。 求大神帮忙,万分感谢~~
31 回复

代码不能手敲么。。。怎么没排版了、、

var db = new sqlite3.Database(‘xxx’); … for(var i=0; i<arr.length; i++){ … db.all(sql1, function(){ … console.log( arr[i] ); db.all(sql2, function(){ … console.log( arr[i] ); }) }) }

@timerainv7 代码部分可以使用 markdown标记。 请参考 在线markdown编辑器 ,里面有示例。 效果是酱紫:

console.log(output);

终极建议,不要用nodejs。nodejs的异步模式非常初级,粗暴,简单,不利于和谐,异步终极的目标是没有异步,没有显式异步的异步才是好异步。

没有显式异步的异步其实就是同步,如果你觉得nodejs的异步模式不好理解,那么可以试一试fibjs的形式同步。

曾经我对nodejs的异步模式深信不移,后来才发现,什么回调,什么promise,什么generator都特么弱爆了,有这个时间去搞这些,还不如coroutine来的清爽实在,协程才是异步的未来,才是将异步代码转为同步代码的正途。大牛们告诉我们,这个世界是并行的,这个世界是异步的,并行,异步才是符合人类思维模式的。但请注意这个观点仅仅适用于大牛。我不属于大牛,我脑子比较简单,我的思维方式是顺序是同步的,我怕耗费无谓的时间在流程控制上,怕花时间在理解generator上,所以我选择形式同步,抛弃了nodejs。

nodejs不改变,不管它版本号变得怎么快(刚出了4.0,立马跑去看,这样的更新能搞一个大版本号?难不成这个版本号是庆祝社区合并?),都不能代表未来,因为我坚信,这个世界普通人还是大多数,大牛毕竟是少数。

如果不像抛弃nodejs,请把回调模式下的异步彻底搞明白了再来写代码,不然死多活少。教你一个理解回调模式下的异步的秘诀,你必须要搞清楚,所有javascript的代码除了回调函数外都是同步执行的,所有带回调的函数只是发出异步命令和注册回调函数,命令发布完毕,就会顺序往下执行,异步命令执行完成之后,调用回调函数。回调函数调用是异步的,但一旦被调用,除非某异步函数发出另一个异步命令,否则回调函数内都是同步执行的。回调函数的作用其实就是保证逻辑顺序,是为了保证同步,从这点上来看,那些宣称世界是并行的异步的大牛其实都是骗子,我们的脑子事实上还没那么发达,我们的思考模式其实还是顺序的,是同步的。回调模式只是比多线程的锁同步要好理解得多,仅此而已(当然性能上也是好得多)。

    var fs = require('fs');
    var a = 0;
    fs.readFile('test.txt',  'utf-8', function(err, data){
        a++;
        console.log('a in:' + a);
    });
    a++;
    console.log('a out:' + a);

下面是我用lua协程的实现,假设代码已被包含在某个协程中。

    local fs = require('fs')
    local a = 0
    data,  err = fs.readFile('test.ext',  'utf-8')
    a = a + 1
    print(a)

fs.readFile实实在在是异步的,基本的原理就是发出异步请求之后将当前协程挂起yield,异步请求完成后将结果resume。 当然在这么简单的运用看不出谁好谁坏的,当有层层回调的时候,协程的威力才会显现出来,一个要时刻考虑回调的执行顺序,参数传递,错误处理等问题,一个就跟写同步代码一样。

async 或者 bluebird ,看看这两个

老生常谈, promise 化就是了,用async, q, bluebird 等做异步处理

如果无法转变编程思想,那还是用Java .net 等编译类的吧

加标识,或者用async.foreach

@pangguoming 然而 Node 也是 JIT 编译。。。

@coordcn 我就习惯了js现在的方式,习惯了事件驱动回调。

@coordcn 按 lua 的写法,es2015 现在能这么写

const fs = require('fs')
let a = 0
let = yield fs.readFile('test.ext',  'utf-8')
a = a + 1
console.log(a)

es2016之后能这么写

const fs = require('fs')
let a = 0
let = await fs.readFile('test.ext',  'utf-8')
a = a + 1
console.log(a)

如果想從語法層面解決這個問題,而且不用等待es新語法實現,我推薦 iced-coffee-script 。現成的 defer await 已經可以用了,編譯出的js兼容現在的node和瀏覽器引擎

https://maxtaco.github.io/coffee-script/

@coordcn

local fs = require('fs')
    local a = 0
    data,  err = fs.readFile('test.ext',  'utf-8')
    a = a + 1
    print(data)

想知道这个打印出什么

@fengmk2

写你这样的代码,如果只是作为普通用户,不需要理解原理,照着做就行了,如果稍微有点追求的,要探求个所以然来,这就要下功夫了。首先必须要透彻了理解回调模式下的异步,因为这些代码背后首先做的就是对回调的包装和转换,这个转换本身理解成本就不小,为什么要这么转换,理解generator是如何将回调异步转为形式同步的成本也不小,这中间就造成了大神与普通用户的区别,当然有的人是醉心于这种不平等的。而我的方案是平的,不需要附加任何额外的代价,不需要死额外的脑细胞,但对眼里只有javascript一种语言的人是无解的。

这里还有人说lua是很low的语言,我只能表示无语,一个优美小巧的学院派语言竟然被称为low,lua和javascript我都很喜欢,但javascript是工程应用的产物,是很多无奈的妥协的产物,虽然很成功,但却是远不及lua简单干净的。

形式同步大家已没有争议,这是大势所趋,javascript也在朝这个方向发展,但历史包袱过重,进化得过于缓慢,协程的引入也搞成了半调子,es6既然已经这样,下个版本干脆直接一步到位得了,回调异步转形式同步,协程是绕不过去的,不管表面上用什么关键字,yield也好,await也罢,都要在异步的时候主动让出CPU,让其他协程来继续。

已经习惯了回调异步的同学,建议你们立即向形式同步转吧,这个趋势不会因为自己已经习惯了而改变,你不改变就会被淘汰。永远要记住,我们要的是异步带来的好处,而不是复杂难懂的异步代码。

@dayuoba

这个打印的就是文件内容

@coordcn 我并不觉得有什么好难理解的 我node的callback tj的co 包括go的goruntine都用的蛮好 不过js的异步确实比较费劲 要多理解理解 go的goruntine和channel基本一次就上手了 也没有报错 自豪地采用 CNodeJS ionic

@wenshiqi0

go的模型比javascirpt的要好。

异步本身不难理解,难的是复杂的逻辑和错误处理,回调模式下的异步这个问题是很头疼的。

co如果仅仅是应用,不需要理解原理, 那的确没什么难的,但能把回调模式下的异步转成形式同步的原理完全搞明白了还是要废点脑子的。田永强的这篇文章不错http://www.infoq.com/cn/articles/generator-and-asynchronous-programming/

我的方法是不需要做这么多转换的,lua天生支持协程,所有的包装在c层面做好了,在lua层面就是同步代码,完全不需要进行任何转换。c层面也很容易理解,我的做法跟openresty是一样的。

@coordcn 我看过那个了 co的我也理解基本要我自己写也没有问题 不过没必要还是用co吧 我现在比较有兴趣自己实现一个协程 自豪地采用 CNodeJS ionic

@wenshiqi0

为什么要实现协程?在javascript里么?javascirpt不是已经提供了generator这个半调子协程了么?

generator里的yield就相当于lua的yield,next相当于resume。

@coordcn 实现来玩而已 没有特别的目的 是c语言和汇编层面的 可能在协程切换上面要用汇编 自豪地采用 CNodeJS ionic

@coordcn 我对任何东西都没有排斥 纯粹的即学即用 兴趣来了就弄 做完这个我准备就去自己实现一个rust的http库~~ 自豪地采用 CNodeJS ionic

@wenshiqi0

c实现协程主要有两种方式,一种是是lua用的longjmp,还有一种是ucontext,fibjs就是自己用汇编实现的ucontext(为了兼容windows平台)

@wenshiqi0

我也喜欢弄新东西,折腾自己,这样不管结果如何,自我提升还是很快的,做http库的话可以交流啊。

@coordcn 我想上下文也自己实现 实现一个最轻量的 自豪地采用 CNodeJS ionic

@coordcn 没办法 rust没有http库 也当熟悉一下rust语法 自豪地采用 CNodeJS ionic

你可以使用nimble, 实现异步逻辑顺序化

嵌套问题,你可以将回调函数独立成一个函数,维护的时候旧舒服多了

以上内容来自node in action, 吴海星译

@coordcnreadFile是异步的嘛?阻塞的异步?

@coordcn 哈哈,我接触后也觉得,nodejs的异步模式用起来非常不爽(因为人的思维也好,事物的发展也好,问题的解决也好,都是过程式的,只在必要时异步就好了)

@dayuoba

当然是异步的,如果是同步的话就没有意义了。readFile调用的同时当前运行的协程会让出CPU给其他协程运行,等readFile异步运行完成后,会将结果压入协程堆栈,协程唤醒继续向下执行。

es6的generator可以模拟这个过程,首先要将原来的异步函数都包装成一个可以传入真实回调函数的函数,

      function readFile(file, options){
        var pass;
        
        fs.readFile(file, options, function(err, data){
            if(pass){
                pass(err, data);
            }
        })
    
        return function (fn){
            pass = fn;
        };
    }

再通过generator.next()来触发执行异步函数,这个过程只是巧妙的利用generator的特性伪装了回调,其实整个异步过程还是通过回调函数来控制的。generator用这种方法只是伪装了协程特点,但不能算是协程,还是主要利用回调来实现的。当然javascript有javascript的难处,船大难掉头,他必须兼容之前的回调代码。

我用lua实现的是不需要兜这么大圈子的,直接了当当成同步函数用就行了,我们只是要异步带来的好处,还管什么异步的形式?只要能痛快的写代码就行了。

@tim1020

nodejs开了个好头,但能不能笑到最后就要看后续的发展了,nodejs是成也javascript败也javascript,javascript编程模型在接下来几个版本可能会剧烈变动,人心如果散了,队伍就不好带了。。。

我对nodejs的评价是,可以学思想,但千万不能被他的形式束缚。nodejs最初的作者那么激烈的反对协程,认为回调才是异步的未来,现在大家被回调折磨几年之后,其实都在思变,不管是generator还是async,显式的异步终将慢慢退出舞台,后面的历史必然会由形式同步来书写。

我们要的是异步的好处,而不是复杂难懂的异步代码。

其实异步完全是协作的结果。。如果一个人干一件事,完全不依赖外界,是没有异步的。。但是要等别热的结果,结果回来前啥都不干就是同步,等结果的时候干干其他事情就是异步了。。其实等结果的时候干点别的事情就是提高时间的利用率。。本身没啥难理解的,只是事情太复杂就麻烦了。。就像杂技演员两只手可以抛起好多个球。。普通人就不行。。这个就是专业水平的问题了。。

回到顶部