才开始学node没多久,遇到了好多问题,感觉最让我困扰的就是异步转同步
- 有一次遇到的问题大概代码是这样的: for(var i=arr.length; i>0; i–){ fun(); if(i==0){ console.log(“ok”) } } 但是我发现每次fun()还没执行完就先输出“OK”。我知道大概是fun()被放进线程池没有立即执行所以要后执行,但是我不想这样。。
- 还有一次在使用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。
- 还有就是写代码的时候不知不觉就嵌套了很多层。。。 求大神帮忙,万分感谢~~
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] ); }) }) }
终极建议,不要用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。 当然在这么简单的运用看不出谁好谁坏的,当有层层回调的时候,协程的威力才会显现出来,一个要时刻考虑回调的执行顺序,参数传递,错误处理等问题,一个就跟写同步代码一样。
@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)
local fs = require('fs')
local a = 0
data, err = fs.readFile('test.ext', 'utf-8')
a = a + 1
print(data)
想知道这个打印出什么
写你这样的代码,如果只是作为普通用户,不需要理解原理,照着做就行了,如果稍微有点追求的,要探求个所以然来,这就要下功夫了。首先必须要透彻了理解回调模式下的异步,因为这些代码背后首先做的就是对回调的包装和转换,这个转换本身理解成本就不小,为什么要这么转换,理解generator是如何将回调异步转为形式同步的成本也不小,这中间就造成了大神与普通用户的区别,当然有的人是醉心于这种不平等的。而我的方案是平的,不需要附加任何额外的代价,不需要死额外的脑细胞,但对眼里只有javascript一种语言的人是无解的。
这里还有人说lua是很low的语言,我只能表示无语,一个优美小巧的学院派语言竟然被称为low,lua和javascript我都很喜欢,但javascript是工程应用的产物,是很多无奈的妥协的产物,虽然很成功,但却是远不及lua简单干净的。
形式同步大家已没有争议,这是大势所趋,javascript也在朝这个方向发展,但历史包袱过重,进化得过于缓慢,协程的引入也搞成了半调子,es6既然已经这样,下个版本干脆直接一步到位得了,回调异步转形式同步,协程是绕不过去的,不管表面上用什么关键字,yield也好,await也罢,都要在异步的时候主动让出CPU,让其他协程来继续。
已经习惯了回调异步的同学,建议你们立即向形式同步转吧,这个趋势不会因为自己已经习惯了而改变,你不改变就会被淘汰。永远要记住,我们要的是异步带来的好处,而不是复杂难懂的异步代码。
@coordcn 我并不觉得有什么好难理解的 我node的callback tj的co 包括go的goruntine都用的蛮好 不过js的异步确实比较费劲 要多理解理解 go的goruntine和channel基本一次就上手了 也没有报错 自豪地采用 CNodeJS ionic
go的模型比javascirpt的要好。
异步本身不难理解,难的是复杂的逻辑和错误处理,回调模式下的异步这个问题是很头疼的。
co如果仅仅是应用,不需要理解原理, 那的确没什么难的,但能把回调模式下的异步转成形式同步的原理完全搞明白了还是要废点脑子的。田永强的这篇文章不错http://www.infoq.com/cn/articles/generator-and-asynchronous-programming/
我的方法是不需要做这么多转换的,lua天生支持协程,所有的包装在c层面做好了,在lua层面就是同步代码,完全不需要进行任何转换。c层面也很容易理解,我的做法跟openresty是一样的。
为什么要实现协程?在javascript里么?javascirpt不是已经提供了generator这个半调子协程了么?
generator里的yield就相当于lua的yield,next相当于resume。
当然是异步的,如果是同步的话就没有意义了。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实现的是不需要兜这么大圈子的,直接了当当成同步函数用就行了,我们只是要异步带来的好处,还管什么异步的形式?只要能痛快的写代码就行了。