ES6的Generator却给异步操作又提供了新的思路,马上就有人给出了如何用Generator来更加优雅的处理异步操作。Generator函数是协程在ES6的实现,最大特点就是可以交出函数的执行权(即暂停执行)。整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。Generator函数的执行方法如下。
function * greneratorDome(){ yield "Hello"; yield "World"; return "Ending"; } let grenDome = greneratorDome(); console.log(grenDome.next()); // {value: "Hello", done: false} console.log(grenDome.next()); // {value: "World", done: false} console.log(grenDome.next()); // {value: "Ending", done: true} console.log(grenDome.next()); // {value: undefined, done: true}
粗略实现Generator
function makeIterator(array) { var nextIndex = 0; return { next: function() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true}; } }; } var it = makeIterator(['a', 'b']); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true }
Async/Await与Generator类似,Async/await是Javascript编写异步程序的新方法。以往的异步方法无外乎回调函数和Promise。但是Async/await建立于Promise之上,个人理解是使用了Generator函数做了语法糖。async函数就是隧道尽头的亮光,很多人认为它是异步操作的终极解决方案。
function a(){ return new Promise((resolve,reject) => { console.log("a函数") resolve("a函数") }) } function b (){ return new Promise((resolve,reject) => { console.log("b函数") resolve("b函数") }) } async function dome (){ let A = await a(); let B = await b(); return Promise.resolve([A,B]); } dome().then((res) => { console.log(res); });
Node.js异步I/O
当我们发起IO请求时,调用的是各个不同平台的操作系统内部实现的线程池内的线程。这里的IO请求可不仅仅是读写磁盘文件,在*nix中,将计算机抽象了一层,磁盘文件、硬件、套接字等几乎所有计算机资源都被抽象为文件,常说的IO请求就是抽象后的文件。完成Node整个异步IO环节的有事件循环、观察者、请求对象。
事件循环机制
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。于是就有一个概念,任务队列。如果排队是因为计算量大,CPU忙不过来,倒也算了,但是很多时候CPU是闲着的,因为IO设备(输入输出设备)很慢(比如Ajax操作从网络读取数据),不得不等着结果出来,再往下执行。
事件循环是Node的自身执行模型,正是事件循环使得回调函数得以在Node中大量的使用。在进程启动时Node会创建一个while(true)死循环,这个和Netty也是一样的,每次执行循环体,都会完成一次Tick。每个Tick的过程就是查看是否有事件等待被处理。如果有,就取出事件及相关的回调函数,并执行关联的回调函数。如果不再有事件处理就退出进程。
线程只会做一件事情,就是从事件队列里面取事件、执行事件,再取事件、再事件。当消息队列为空时,就会等待直到消息队列变成非空。而且主线程只有在将当前的消息执行完成后,才会去取下一个消息。这种机制就叫做事件循环机制,取一个消息并执行的过程叫做一次循环。
while(true) { var message = queue.get(); execute(message); }
我们可以把整个事件循环想象成一个事件队列,在进入事件队列时开始对事件进行弹出操作,直至事件为0为止。
process.nextTick
process.nextTick()方法可以在当前"执行栈"的尾部-->下一次Event Loop(主线程读取"任务队列")之前-->触发process指定的回调函数。也就是说,它指定的任务总是发生在所有异步任务之前,当前主线程的末尾。(nextTick虽然也会异步执行,但是不会给其他io事件执行的任何机会);
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function C() { console.log(3'); }, 0); // 1 // 2 // 3
异步过程的构成要素
异步函数实际上很快就调用完成了,但是后面还有工作线程执行异步任务,通知主线程,主线程调用回调函数等很多步骤。我们把整个过程叫做异步过程,异步函数的调用在整个异步过程中只是一小部分。