资讯专栏INFORMATION COLUMN

学习JavaScript异步、事件循环

nemo / 1070人阅读

摘要:使用关键字来表示,在函数内部使用来表示异步。执行完了后,执行栈再次为空,事件触发线程会重复上一步操作,再取出一个消息队列中的任务,这种机制就被称为事件循环机制。

async 函数是 Generator 函数的语法糖。使用 关键字 async 来表示,在函数内部使用 await 来表示异步。想较于 Generator,Async 函数的改进在于下面四点:

内置执行器 Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样

更好的语义 async 和 await 相较于 * 和 yield 更加语义化

更广的适用性 co 模块约定,yield 命令后面只能是Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者原始类型的值(Number,string,boolean,但这时等同于同步操作)

返回值是 Promise async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用

await命令:正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值

下面给大家看一道之前看过的题:

function test1() {
    console.log("执行test1");
    return "test1";
}

 function test2() {
    console.log("执行test2");
    return Promise.resolve("hello test2");
}

async function asyncTest() {
    console.log("asyncTest start...");
    const v1 = await test1();
    console.log(v1);
    const v2 = await test2();
    console.log(v2);
    console.log(v1, v2);
}

setTimeout(function(){
    console.log("setTimeout")
},0)  

asyncTest();


new Promise(function(resolve){
    console.log("promise1")
    resolve();
}).then(function(){
    console.log("promise2")
})
console.log("test end")

这道题结合了setTimeout、async、promise异步函数,根据三种不同异步任务执行顺序可以学习js引擎的事件循环机制,咱们先看下结果:

test start...
执行test1
promise1
test end
test1
执行test2
promise2
hello test2
test1,hello test2
setTimeout

再讲答案之前先理解以下几个概念:

事件循环与消息队列

JS引擎线程遇到异步(DOM事件监听、网络请求、setTimeout计时器等...),会交给相应的线程多带带去维护异步任务,等待某个时机(计时器结束、网络请求成功、用户点击DOM),然后由 事件触发线程 将异步对应的 回调函数 加入到消息队列中,消息队列中的回调函数等待被执行。

同时,JS引擎线程会维护一个 执行栈,同步代码会依次加入执行栈然后执行,结束会退出执行栈。

如果执行栈里的任务执行完成,即执行栈为空的时候(即JS引擎线程空闲),事件触发线程才会从消息队列取出一个任务(即异步的回调函数)放入执行栈中执行。

消息队列是类似队列的数据结构,遵循**先入先出(FIFO)**的规则。

执行完了后,执行栈再次为空,事件触发线程会重复上一步操作,再取出一个消息队列中的任务,这种机制就被称为事件循环(event loop)机制。

主代码块(script)依次加入执行栈,依次执行,主代码块为:

setTimeout()

asyncTest()

Promise()

console.log("test end")

宏任务与微任务

macrotask(宏任务) :主代码块、setTimeout、setInterval等(可以看到,事件队列中的每一个事件都是一个 macrotask,现在称之为宏任务队列

和 microtask(微任务):Promise、process.nextTick等

JS引擎线程首先执行主代码块。
每次执行栈执行的代码就是一个宏任务,包括任务队列(宏任务队列)中的,因为执行栈中的宏任务执行完会去取任务队列(宏任务队列)中的任务加入执行栈中,即同样是事件循环的机制。
在执行宏任务时遇到Promise等,会创建微任务(.then()里面的回调),并加入到微任务队列队尾。
microtask必然是在某个宏任务执行的时候创建的,而在下一个宏任务开始之前,浏览器会对页面重新渲染(task >> 渲染 >> 下一个task(从任务队列中取一个))。同时,在上一个宏任务执行完成后,渲染页面之前,会执行当前微任务队列中的所有微任务。
也就是说,在某一个macrotask执行完后,在重新渲染与开始下一个宏任务之前,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

执行机制:

执行一个宏任务(栈中没有就从事件队列中获取)

执行过程中如果遇到微任务,就将它添加到微任务的任务队列中

宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)

当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染

渲染完毕后,JS引擎线程继续,开始下一个宏任务(从宏任务队列中获取)

遇到异步函数 setTimeout,交给定时器触发线程 setTimeout加入宏任务队列,JS引擎线程继续,出栈;

执行异步函数asyncTest,首先打印test start...

执行await test1函数首先打印"执行test1",await让出线程去执行后面的代码;

执行Promise 首先打印promise1,then后面函数为微任务,添加到微任务队列中

JS引擎线程继续向下执行同步代码console.log("test end")打印"test end"

回到asyncTest执行await test1由于返回不是promise对象,所以直接返回test1

执行await test2()同样先打印 "执行test2",由于test2返回promise对象 会加入到之前微任务队列中,await继续让出

执行微任务队列,由于任务队列遵循先进先出结果,所以首先打印promise2,然后打印hello test2

微任务队列执行完成后继续执行asyncTest内 await之后的代码打印 俩个await返回的值 --test1,hello test2

最后回到宏任务队列执行setTimeout,打印setTimeout

如果我把test1变成异步函数,大家再思考一下会打印什么结果:

async  function test1() {
    console.log("执行test1");
    return "test1";
}

 function test2() {
    console.log("执行test2");
    return Promise.resolve("hello test2");
}

async function asyncTest() {
    console.log("asyncTest start...");
    const v1 = await test1();
    console.log(v1);
    const v2 = await test2();
    console.log(v2);
    console.log(v1, v2);
}

setTimeout(function(){
    console.log("setTimeout")
},0)  

asyncTest();


new Promise(function(resolve){
    console.log("promise1")
    resolve();
}).then(function(){
    console.log("promise2")
})
console.log("test end")

以上就是此代码执行过程,由于本人也是在学习总结中,如有不对的地方请指教,共同学习,一起进步!!!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99715.html

相关文章

  • 理解异步JavaScript

    摘要:当函数结束,将会被从调用栈移出。事件循环事件循环的责任就是查看调用栈并确定调用栈是否为空。事件循环会再次检查调用栈是否为空,如果为空的话,它会把事件回调压入栈中,然后回调函数则被执行。 写在文章前 这篇文章是翻译自Sukhjinder Arora的Understanding Asynchronous JavaScript。这篇文章描述了异步和同步JavaScript是如何在运行环境中,...

    ixlei 评论0 收藏0
  • Javascript 事件循环event loop

    摘要:现实中是这样的执行结果为结果告诉我们,是单线程没错,不过不是逐行同步执行。搜索了很多官方个人博客得到了一堆词引擎主线程事件表事件队列宏任务微任务,彻底懵逼。。。以此规则不停的执行下去就是我们所听到的事件循环。 都知道javascript是单线程,那么问题来了,既然是单线程顺序执行,那怎么做到异步的呢? 我们理解的单线程应该是这样的,排着一个个来,是同步执行。 showImg(https...

    Miyang 评论0 收藏0
  • 这一次,彻底弄懂 JavaScript 执行机制

    摘要:事件完成,回调函数进入。主线程从读取回调函数并执行。终于执行完了,终于从进入了主线程执行。遇到,立即执行。宏任务微任务第三轮事件循环宏任务执行结束,执行两个微任务和。事件循环事件循环是实现异步的一种方法,也是的执行机制。 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果读完本文还不懂,可以揍我。不论你是javascript新手还是老鸟,不论是面试求职,还是日常开发工作...

    dreambei 评论0 收藏0
  • 事件循环机制

    摘要:事件触发线程主要负责将准备好的事件交给引擎线程执行。进程浏览器渲染进程浏览器内核,主要负责页面的渲染执行以及事件的循环。第二轮循环结束。 将自己读到的比较好的文章分享出来,大家互相学习,各位大佬有好的文章也可以留个链接互相学习,万分感谢! 线程与进程 关于线程与进程的关系可以用下面的图进行说明: showImg(https://segmentfault.com/img/bVbjSZt?...

    Blackjun 评论0 收藏0
  • 事件循环机制

    摘要:事件触发线程主要负责将准备好的事件交给引擎线程执行。进程浏览器渲染进程浏览器内核,主要负责页面的渲染执行以及事件的循环。第二轮循环结束。 将自己读到的比较好的文章分享出来,大家互相学习,各位大佬有好的文章也可以留个链接互相学习,万分感谢! 线程与进程 关于线程与进程的关系可以用下面的图进行说明: showImg(https://segmentfault.com/img/bVbjSZt?...

    CloudwiseAPM 评论0 收藏0

发表评论

0条评论

nemo

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<