异步 async/await深入探究

    • async/await题目
    • Generator函数与promise函数的区别?
    • co 函数库?
    • 异步I/O操作?
    • 异步与被委托?
    • javaScript 是单线程运行机制,无论同步和异步最终还是单线程的,异步只是把代码运行时间长的延迟了,被延迟的时间里去执行其他代码而已。是这样吗?
    • 除了委托给浏览器、事件循环、操作系统之外,还有别的吗?
    • Web Worker是一种运行在后台的JavaScript线程。JS是一个单线程,为什么Web Worker是一个可以运行在后台的JavaScript线程?那岂不是同时有了两个JS线程吗?
    • async 函数到底是Promise的语法糖还是 Generator 函数的语法糖?

async/await题目

如果两个await在一个async里面,那么第二个await需要等待第一个await执行完才执行,整个async函数会停止执行,而主线程去执行async函数之外的任务
比如:案例1中,b会等a执行完。

//案例1:async function text() { let b= 0;let a = 0;console.log('开始')a = await new Promise(resolve => {setTimeout(() =>{console.log("a:" + a)resolve(3);}, 3000);});b = await new Promise(resolve => {setTimeout(() =>{console.log("b:" + b)resolve(3);}, 3000);});let c = Promise.all([a,b])console.dir("c:" + c,{depth:null});// console.log("a:" + a);}text();console.log(5)
//答案:开始//async函数并不一开始就立刻进入了事件循环,遇到await的时候才进入事件循环5 a:0//在'开始'执行完之后,遇到await了,await后面的代码都暂停执行,把async函数放进消息队列,但是记住执行到了第一个await这里,直到上一个5执行完,就是主线程任务执行完,再来执行ab:0//又遇到了await,又把整个async函数放进事件循环,但是记住执行到了第二个await,等待3秒之后回来继续执行'c:[object Promise]'

开始//async函数并不一开始就立刻进入了事件循环,遇到await的时候才进入事件循环
5
a:0//在’开始’执行完之后,遇到await了,await后面的代码都暂停执行,把async函数放进消息队列,但是记住执行到了第一个await这里,直到上一个5执行完,就是主线程任务执行完,再来执行a
b:0//又遇到了await,又把整个async函数放进事件循环,但是记住执行到了第二个await,等待3秒之后回来继续执行
‘c:[object Promise]’//最后一个promise执行完就立刻输出

如果两个await分别在两个async里面,那么a,b同时执行
比如:案例2中:同时输出

// 案例2:async function a() {await new Promise(resolve => {setTimeout(() =>{console.log("a finished")resolve(3);}, 1000);});}async function b() {await new Promise(resolve => {setTimeout(() =>{console.log("b finished")resolve(3);}, 1000);});}Promise.all([a(), b()]).then(() => {console.log("all finished");});
//答案:a,b,c几乎同时输出,c其实等了a,b中最慢的一个a finishedb finishedall finished

如果两个定时器在主线程,没有用await,也不在async里面,那么也是同时执行开启两个定时器,不互相等待
比如:案例3中:同时输出

// 案例3:setTimeout(() =>{console.log("a finished")}, 1000);setTimeout(() =>{console.log("b finished")}, 1000);
//答案:也是几乎同时输出,下面的定时器没有等待上面的定时器执行完a finishedb finished

总结:await关键字对导致async函数的局部阻塞(暂时停止执行),但是不会阻塞主线程。

当执行到await关键字的时候,整个async函数里面await后面的语句都会等待前面的await的执行完毕。

但是在async之外的主线程任务,继续执行,并不阻塞


async函数在执行时会被加入到事件循环中,但是其执行的具体机制与其他函数并不完全相同。具体来说,async函数在执行时会被分为若干个微任务(microtask),这些微任务会被加入到事件循环的微任务队列中。在主线程执行完当前任务后,会立即执行微任务队列中的所有微任务,然后才会执行下一个宏任务。

因此,在下面这段代码中,text函数的执行结果并不会等待主线程中的所有任务都执行完毕后才输出,而是会被加入到微任务队列中,在主线程执行完当前任务后立即执行。

async function text() {console.log('Hello');await Promise.resolve();console.log('World');}console.log('Start');text();console.log('End');
StartHelloEndWorld

其中,首先输出”Start”,然后调用text函数,并输出”Hello”。在text函数中,使用await关键字等待一个Promise对象的resolve结果,这使得text函数的执行被分为两个微任务。当主线程执行完当前任务后,立即执行第一个微任务,输出”World”。然后,执行第二个微任务,结束text函数的执行,最后输出”End”。


Generator函数与promise函数的区别?

虽然Generator函数和Promise函数都可以用于异步编程,但它们之间有以下几个主要的区别:

  1. 返回值类型不同:Generator函数的返回值是一个迭代器对象,而Promise函数的返回值是一个Promise对象。
  2. 异步操作的实现方式不同:Generator函数是通过yield关键字实现异步操作的暂停和恢复,而Promise函数是通过then和catch方法实现异步操作的链式调用。
  3. 错误处理方式不同:Generator函数可以使用try…catch语句来捕获异步操作中的错误,而Promise函数可以使用catch方法来处理异步操作中的错误。
  4. 编程风格不同:Generator函数通常需要配合使用生成器对象的next()方法来实现迭代操作,代码的逻辑较为复杂,而Promise函数则采用链式调用的方式,代码较为简洁。

需要注意的是,Generator函数和Promise函数并不是互斥的,它们可以在某些情况下共同使用。例如,在使用co函数库时,可以将Generator函数的执行结果转换成Promise对象,然后使用then方法进行链式调用。此外,在ES2017的Async/Await语法中,也可以使用Promise函数来实现异步操作。

co 函数库?

co函数库是一个用于简化Generator函数的执行的工具库,它可以将Generator函数自动执行,并将yield关键字后面的表达式转换成Promise对象。使用co函数库,可以在不修改Generator函数的代码的情况下,将其转换成一个普通的异步函数,并使用async/await语法来进行调用。

co函数库的基本用法如下:

const co = require('co');function* gen() {const result1 = yield asyncOperation1();const result2 = yield asyncOperation2(result1);return result2;}co(gen).then(result => {console.log(result);}).catch(error => {console.error(error);});

在上面的例子中,gen函数是一个Generator函数,其中包含两个异步操作。使用co函数库,可以将gen函数自动执行,并将yield关键字后面的表达式转换成Promise对象。在执行完所有异步操作后,co函数库会将Generator函数的最终结果返回给调用方。

需要注意的是,使用co函数库时,必须保证Generator函数中的所有异步操作都返回Promise对象,否则co函数库无法正常工作。此外,co函数库只支持Generator函数,不支持async/await函数。如果需要使用async/await函数,可以考虑使用async函数或Promise对象来替代Generator函数。

异步I/O操作?

需要注意的是,在执行异步I/O操作时,JavaScript的线程并不是真正意义上的“后台”,而是通过委托给操作系统或其他线程来完成I/O操作的。

异步与被委托?

所有的异步任务之所以可以是异步的,是因为它们都被委托给除了JS引擎之外的设备去做了,对吗?比如,I/O被委托给操作系统,HTTP请求被委托给了谁呢?

基本正确。在JavaScript中,异步任务之所以可以是异步的,是因为它们通常是被委托给其他设备或者系统来执行的,而不是由JavaScript引擎本身来执行的。

例如,在浏览器中,HTTP请求通常是通过XMLHttpRequest或Fetch API来实现的。这些API会发送HTTP请求到服务器,并在收到响应后通知JavaScript程序。实际上,这些API将HTTP请求委托给浏览器的底层网络库来执行,而不是由JavaScript引擎本身来执行。底层网络库通常是由操作系统提供的。

定时器委托个浏览器的一个专门处理定时器的线程

在Node.js中,异步I/O操作也是通过将任务委托给底层系统来实现的。例如,在Node.js中,文件读写和网络通信等操作都是通过底层libuv库来完成的。这个库会将I/O操作委托给操作系统来执行,而不是由JavaScript引擎本身来执行。

需要注意的是,JavaScript引擎本身并不能直接执行I/O操作等耗时的任务,因为这样会导致JavaScript程序阻塞。因此,将任务委托给其他设备或系统来执行是提高JavaScript程序性能和响应速度的常用方式。

javaScript 是单线程运行机制,无论同步和异步最终还是单线程的,异步只是把代码运行时间长的延迟了,被延迟的时间里去执行其他代码而已。是这样吗?

部分正确。JavaScript 是单线程执行的,无论同步或异步代码最终还是在一个线程中执行。但是,异步不是仅仅是把代码的运行时间延迟了,而是把代码的执行交给了其他的线程或者进程来执行,从而让 JavaScript 主线程可以继续执行其他代码,以此来实现非阻塞的异步编程。

举个例子,当 JavaScript 调用一个异步 I/O 操作时,比如发起一个 Ajax 请求或者从磁盘读取文件,这个操作被委托给浏览器的底层系统来处理。JavaScript 主线程并不会一直等待这个操作的结果返回,而是继续执行下面的代码。当异步 I/O 操作完成时,浏览器底层系统会通知 JavaScript 程序,把结果传递给回调函数,JavaScript 主线程再通过回调函数来处理结果。这个过程中,JavaScript 主线程并没有被阻塞,而是继续执行其他代码。因此,异步可以让 JavaScript 程序更加高效地利用 CPU 和 I/O 资源,提升程序的响应速度和性能。

除了委托给浏览器、事件循环、操作系统之外,还有别的吗?

除了委托给浏览器、事件循环、操作系统之外,异步任务还可以委托给其他的线程或进程来完成,比如:

  1. Web Worker:Web Worker是一种运行在后台的JavaScript线程,它可以执行一些耗时的计算任务,而不会阻塞UI线程。我们可以通过Worker API创建一个Web Worker,然后将任务委托给它来执行,Web Worker执行完任务后,会将结果返回给主线程,主线程可以在回调函数中处理这个结果。

  2. Node.js中的child_process模块:在Node.js中,我们可以使用child_process模块来创建子进程,将一些耗时的计算任务委托给子进程来完成,然后将结果返回给主进程,主进程可以在回调函数中处理这个结果。

总之,异步任务可以委托给不同的线程或进程来完成,具体取决于不同的平台和编程语言。无论如何,委托给其他线程或进程来完成异步任务可以提高程序的并发性和性能,避免了阻塞主线程,从而提高了程序的响应速度。

Web Worker是一种运行在后台的JavaScript线程。JS是一个单线程,为什么Web Worker是一个可以运行在后台的JavaScript线程?那岂不是同时有了两个JS线程吗?

虽然JavaScript是单线程的,但是Web Worker并不是运行在主线程中,而是运行在另一个单独的线程中。这个线程由浏览器创建和管理,可以在后台执行一些耗时的计算任务,而不会阻塞UI线程。因此,可以将Web Worker看作是运行在另一个线程中的JavaScript程序,与主线程相互独立,互不干扰。

在JavaScript中,Web Worker通过使用worker对象来与主线程进行通信。worker对象提供了一个postMessage方法,可以向主线程发送消息,也可以通过onmessage事件监听主线程发送过来的消息。当Web Worker完成任务后,可以将结果通过postMessage方法发送给主线程,主线程可以在onmessage事件中处理这个结果。

需要注意的是,由于Web Worker运行在另一个线程中,因此它不能直接访问DOM和其他主线程的API,需要通过postMessage方法来与主线程进行通信。另外,由于每个Web Worker都是一个独立的线程,它们之间也不能直接共享数据,需要使用消息传递机制来共享数据。

async 函数到底是Promise的语法糖还是 Generator 函数的语法糖?

async 函数是 Promise 的语法糖,不是 Generator 函数的语法糖。虽然 async 函数和 Generator 函数都可以让我们异步地处理数据,但它们的实现方式和使用方法都有所不同。

async 函数内部实现了 Promise,其返回值是一个 Promise 对象,可以使用 then() 方法进行链式调用。而 Generator 函数需要通过手动控制迭代器来完成异步操作,并且返回的也不是 Promise 对象。

async 函数在使用上也更为简单直观,只需要在函数前加上 async 关键字,然后在需要等待的异步操作前面加上 await 关键字即可,语法清晰明了。而 Generator 函数则需要手动调用迭代器的 next() 方法来实现异步操作,语法较为复杂。