最大卖点的事件机制Nodejs和异步IO不是透明的开发。开发人员需要编写异步获取销售点的代码,和点也由一些Nodejs对手攻击。但在任何情况下,异步编程是NodeJS的最大特征,而无需掌握异步编程,就不能说真正学到了Nodejs。本章将介绍各种异步编程的相关知识。

在代码中,异步编程的直接体现是回调。异步编程依赖回调,但不能说回调后使用,程序是异步的。首先,我们可以看看下面的代码。


功能heavycompute(n,回调){
var计数= 0,
我,J;

对于(i = n;i 0;;i){
对于(j = n;j 0;;j){
计数= 1;
}
}

回调(计数);
}

heavycompute(10000,函数(计数){)
console.log(计数);
});

console.log(你好);





一亿
你好



你可以看到,在上面的代码中的回调函数仍然在随后的code.js本身运行在一个单独的线程,它是不可能运行其他代码时,代码段没有运行完毕,所以没有异步执行的概念。

但是,如果某个函数做了某件事情来创建一个线程或进程,并且完成一些与JS主线程并行的事情,并且在完成后通知JS主线程,情况就不同了。


setTimeout(){()函数(
console.log(世界的);
},1000);

console.log(你好);





你好
世界





这个时候你可以看到,回调函数之后的代码后执行。如上所述,js本身并不是一个线程,异步执行的,所以我们可以认为特殊功能所提供的运行时环境的setTimeout JS规范要做的是创造一个平行线后立即归还,让JS的主要过程然后可以执行后续代码,然后执行回调函数中后的并行处理收到通知。除了setTimeout和setInterval,这种功能还包括异步API,如fs.readfile,由Nodejs。

此外,我们仍然回到JS在单个线程上运行的事实,这决定了js在执行一段代码之前不能执行包括回调函数在内的其他代码,也就是说,即使并行线程完成了,它也会通知JS主线程执行回调函数。回调函数在JS主线程空闲时才启动。


功能heavycompute(N){
var计数= 0,
我,J;

对于(i = n;i 0;;i){
对于(j = n;j 0;;j){
计数= 1;
}
}
}

新的日期();

setTimeout(){()函数(
console.log(新)-(T);
},1000);

(50000)heavycompute;





八千五百二十





如您所见,回调函数应该在1秒后调用,因为JS主线程忙于运行其他代码,实际执行时间已大大延迟。

代码设计模式

异步编程有许多独特的代码设计模式。为了达到相同的功能,同步和异步的方式有很大的不同。

函数的返回值

使用一个函数的输出作为另一个函数的输入是一个常见的要求,并且同步地,代码通常是用下面的方式编写的:


无功输出= FN1(FN2(输入的));
做某事。



在异步模式下,因为函数执行结果不是通过返回值传递的,所以它是通过回调函数传递的,所以代码通常是用下面的方式编写的:


Fn2(输入功能(输出){
FN1(输出,功能(输出电压){
做某事。
});
});



你可以看到,这是一个回调函数设置多个回调,也更容易写出代码的形状。

foreach

在遍历数组时,使用函数对数据成员依次进行处理也是很常见的。


VaR len = arr.length,
我= 0;

为了(;;我;
ARR {我} =同步(ARR {我});
}

数组项已处理。/ / All




如果函数是异步执行的,上面的代码不能保证所有的数组成员在循环结束后完成。如果一个数组成员必须一个接一个地处理,那么异步代码通常是用下面的方式编写的:


(下一个函数(i,莱恩,回调){)
如果(i){ {
异步(ARR {我},{功能(价值)
ARR {我} =价值;
下一步(i + 1,莱恩,回调);
});
{人}
回调();
}
}(0,arr.length,函数(){)
数组项已处理。/ / All
});



可以看出,上述代码在异步函数上执行一次,并在将结果发送给下一个数组成员并开始下一轮执行之前返回执行结果,直到处理数组的所有成员,然后通过回调触发后续代码的执行。

如果数组成员可以并行处理,但下面的代码仍然需要所有数组成员进行处理,那么异步代码将被调整为以下形式。


函数(i,莱恩,计数,回调){
为了(;;我;
(函数(i){)
异步(ARR {我},{功能(价值)
ARR {我} =价值;
如果(+计数=透镜){
回调();
}
});
}(一);
}
}(0,0,arr.length,函数(){){
数组项已处理。
});



你可以看到,与异步串行遍历的版本相比,上面的代码并行化的所有阵列成员,和判断的时候所有的数组成员进行处理。

异常处理

js本身提供了一个异常捕获和处理机制,尝试…..,它只能用于同步执行的代码。


函数同步(FN){
返回FN();
}

{试
同步(空);
做某事。
} catch(错误){
console.log(错误:%s,错误信息);
}





错误:对象不是函数



你可以看到,例外的是泡在代码执行路径直到他们被捕获时,第一次尝试语句时。但由于异步功能中断代码的执行路径,当异步函数是异步函数的执行过程中产生的位置执行路径被中断,如果一个try语句没有遇到的,它将被作为一个全球性的例外。下面是一个例子。


功能(FN,异步回调){
执行路径在此中断。代码
setTimeout(){()函数(
回调(FN());
},0);
}

{试
异步(null,函数(数据){
做某事。
});
} catch(错误){
console.log(错误:%s,错误信息);
}





4
回调(FN());
^
TypeError:不是一个函数对象
在空。_ontimeout( /家/用户/测试。JS:4:13)
在timer.listontimeout {等}(定时器事件。JS:110:15)



由于代码执行路径被中断,所以我们需要在异常气泡被吹到断点之前,用尝试语句捕获异常,并通过回调函数捕获捕获的异常。


功能(FN,异步回调){
执行路径在此中断。代码
setTimeout(){()函数(
{试
回调(NULL,FN());
} catch(错误){
回调(错误);
}
},0);
}

异步(null,函数(呃,数据){
如果(错误){
console.log(错误:%s,错误信息);
{人}
做某事。
}
});





错误:对象不是函数



正如你所看到的,唯一的例外是又被抓住了。在NodeJS,几乎所有的异步API是以同样的方式设计,并在回调函数的第一个参数是错误的,所以当我们写我们自己的异步功能,我们也可以处理这样的异常,与NodeJS设计风格一致。

与异常处理,我们可以再想想我们写的代码一般。基本上,我们的代码是做什么的,然后调用一个函数,然后做一些事情,然后调用一个函数,如此循环。如果我们写同步代码,你只需要写一个try语句在代码入口点来捕获所有异常的鼓泡。示例如下。


函数main(){
做某事。
synca();
做某事。
SyncB();
做某事。
SyncC();
}

{试
主();
} catch(错误){
与例外。交易
}




但是如果我们编写异步代码,这是唯一的事情。因为每个异步函数调用都会中断代码执行路径,只能通过回调函数来传递异常,所以我们需要在每个回调中判断是否有异常,这时只有三个异步函数调用,才会产生下面的代码。


函数main(回调){
做某事。
asynca(功能(呃,数据){
如果(错误){
回调(错误);
{人}
做某事
AsyncB(功能(呃,数据){
如果(错误){
回调(错误);
{人}
做某事
AsyncC(功能(呃,数据){
如果(错误){
回调(错误);
{人}
做某事
回调(null);
}
});
}
});
}
});
}

主(函数(错误){)
如果(错误){
与例外。交易
}
});




如您所见,回调函数使代码复杂,处理异常的异步方式使代码更加复杂。