轮询回调函数js 等待条件成立后执行

874 阅读5分钟
<html> 
 
<head> 
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<title>回调函数(callback)</title> 
<script language="javascript" type="text/javascript"> 
 
function b(text){ 
console.log(text,333);
}
function test(){ 
    //a(b);
	delay(b,"#input1","直的吗 要要要");//b为回函数,第三个为传入参数给b使用
}
//作用:传入一个函数作为参数,当满足某个选器长度大于1时,回调函数起作用
//参数:callback 回调函数
//参数:strId id选择器名字
//调用:delay(b,"#input1");
var delay = function(callback,strId,text){
    if($(strId).val().length>0){
		callback(text);
		console.log($(strId).val())
		return;
	}
	else{setTimeout(function(){delay(callback,strId,"等待")}, 100)}
}
 
</script> 
 
</head> 
 
<body> 
<h1>学习js回调函数</h1> 
<button id="btn" onClick=test()>click me</button> 
<p>应该能看到调用了两个回调函数</p> 
</body> 
<input type="text" value="" id="input1" />
</html> 

备注: 回调,是非常基本的概念,尤其在现今NodeJS诞生与蓬勃发展中变得更加被人们重视。很多朋友学NodeJS,学很久一直摸不着门道,觉得最后在用Express写Web程序,有这样的感觉只能说明没有学懂NodeJS,本质上说不理解回调,就不理解NodeJS。

NodeJS有三大核心: 
CallBack回调 
Event事件 
Stream流

先来看什么不叫回调,下面是很多网友误认为的回调:

复制代码

//代码示例1
//Foo函数意在接收两个参数,任意类型a,和函数类型cb,在结尾要调用cb()
function Foo(a, cb){
    console.log(a);
    // do something else
    // Maybe get some parameters for cb
    var param = Math.random();
    cb(param);
}
//定义一个叫CallBack的函数,将作为参数传给Foo
var CallBack = function(num){
    console.log(num);
}
//调用Foo
Foo(2, CallBack);

复制代码

以上代码不是回调,以下指出这里哪些概念容易混淆: 
变量CallBack,被赋值为一个匿名函数,但是不因为它名字叫CallBack,就称知为回调 
Foo函数的第二个形式参数名为cb,同理叫cb,和是不是回调没关系 
cb在Foo函数代码最后被以cb(param)的形式调用,不因为cb在另一个函数中被调用,而将其称之为回调

直白来讲,以上代码就是普通的函数调用,唯一特殊一点的地方是,因为JS有函数式语言的特性,可以接收函数作为参数。在C语言里可以用指向函数的指针来达到类似效果。

讲到这里先停一下,大家注意到本文的标题是解读异步、回调和EventLoop,回调之前还有异步呢,这个顺序对于理解很有帮助,可以说理解回调的前提,是理解异步。

说到异步,什么是异步呢?和分布、并行有什么区别?

回归原始,追根溯源是我们学习编程的好方法,不去想有什么高级的工具和概念,而去想如果我们只有一个浏览器做编译器和一个记事本,用plain JS写一段异步代码,怎么写?不能用事件系统,不能用浏览器特性。

小明:刚才上面那段代码是异步的吗? 
老袁:当然不是,即便把Foo改为AsyncFoo也不是。这里比较迷惑的是cb(param)是在Foo函数的最后被调用的。 
小明:好像觉得异步的代码,确实应该在最后调一个callback函数,因为之后的代码不会被执行到了。 
老袁:异步的一个定义是函数调用不返回原来代码调用处,而cb(params)调用完后,依旧返回到Foo的尾部,即便cb(params)后还有代码,它们也可以被执行到,这是个同步调用。

Plain JS 异步的写法有很多,以经典的为例:

复制代码

//代码示例2
// ====同步的加法
function Add(a, b){
    return a+b;
}
Add(1, 2) // => 3

// ====异步的加法
function LazyAdd(a){
    return function(b){
        return a+b;
    }
}
var result = LazyAdd(1); // result等于一个匿名函数,实际是闭包
//我们的目的是做一个加法,result中保存了加法的一部分,即第一个参数和之后的运算规则,
//通过返回一个持有外层参数a的匿名函数构成的闭包保存至变量result中,这部是异步的关键。
//极端的情况var result = LazyAdd(1)(2);这种极端情况又不属于异步了,它和同步没有区别。

// 现在可以写一些别的代码了
    console.log('wait some time, doing some fun');
// 实际生产中不会这么简单,它可能在等待一些条件成立,再去执行另外一半。

result = result(2) // => 3

复制代码

 

上述代码展示了,最简单的异步。我们要强调的事,异步是异步,回调是回调,他俩半毛钱关系都没有。

Ok,下面把代码改一改,看什么叫回调:

复制代码

//代码示例3
//注意还是那个Add,精髓也在这里,随后说到
function Add(a, b){
    return a+b;
}
//LazyAdd改变了,多了一个参数cb
function LazyAdd(a, cb){
    return function(b){
        cb(a, b);
    }
}
//将Add传给形参cb
var result = LazyAdd(1, Add)
// doing something else
result = result(2); // => 3

复制代码

这段代码,看似简单,实则并不平凡。

小明:这代码给人的第一感觉就是脱裤子放屁,明明一个a+b,先是变成异步的写法就多了很多代码,人都看不懂了,现在的这个加了所谓的“回调”,更啰嗦了,最后得到的结果都是1+2=3,尼玛这不有病吗? 
老袁:你只看到了结果,却不知道为什么人家这么写,这样写为了什么。代码示例2和3中,同样的Add函数,作为参数传到LazyAdd中,此时它是回调。那为什么代码示例1中,Foo中传入的cb不是回调呢?要仔细体会这句话,需要带状态的才叫回调函数,own state,这里通过闭包保存的a就是状态。 
小明:我伙呆 
老袁:现在再说为什么要有回调,单看输出结果,回调除了啰嗦和难于理解之外没有任何意义。但是!!!

现在说吧,CallBack的好处是:保证API不撕裂 
也就是说,异步是很有需求的,处理的好能使计算效率提高,不至于卡在某处一直等待。但是异步的写法,我们看到了非常难看,把一个加法变成异步,都如此难看,何况其他。那么CallBack的妙处就是“保证API不撕裂”,代码中写到的精髓所在,还是那个Add,对,让程序员在写异步程序的时候,还能够像同步写法那样好理解,Add作为CallBack传入,保证的是Add这个方法好理解,作为API设计中的重要一个环节,保证开发者用起来方便,代码可读性高。

以NodeJS的readFile API为例进一步说明: 
fs.readFile(filename, [options], callback) 
有两个必填的参数filename和callback 
callback是实际程序员要写代码的地方,写它的时候假设文件已经读取到了,该怎么写还怎么写,是API历史上的一次大进步。

//读取文件'etc/passwd',读取完成后将返回值,传入function(err, data) 这个回调函数。
fs.readFile('/etc/passwd', function (err, data) {
  if (err) throw err;
  console.log(data);
});

回调和闭包有一个共同的特性:在最终“回调 ”调用以前,前面所有的状态都得存着。