初学web对js异步机制的认识

本文解释了JavaScript初学者对异步编程的常见误解,特别是setTimeout和Promise的工作原理,强调了在浏览器环境中并非所有操作都在单线程内执行,而是通过事件循环处理异步任务,如宏任务和微任务的顺序执行。
摘要由CSDN通过智能技术生成

初学js时,正好在学校里学习线程等概念,对于异步js这一块始终有许多误解导致无法理解代码运行机制,于是根据自己的理解记下笔记。

刚开始学的时候,所有资料都告诉我js是单线程语言,结果在看到setTimeOut这个API的时候我疑惑了许久,觉得既然js是单线程语言,那么这个setTimeOut就应该在一个循环里面不停计时直到满足给定时间,然后再执行给定的回调才对,而至于写在setTimeOut调用之后的语句,自然也应该在等待到给定时间后再执行才对。但是代码运行的结果却仿佛js新开了一个“计时线程”一样,由此在相当长时间里,我对js异步机制处于一个云里雾里的状态。

事实上,在浏览器环境中,不只有js线程在运行,同时也有许多其他线程。setTimeout其实是调用了Web API,浏览器会在js线程以外的部分完成计时任务。浏览器中js中与异步有关的方法,譬如setTimeout、addEventListner,都并不是创建了一个新的线程在来判断回调执行时机并执行回调,这些通知回调执行的工作是由Web API来完成的。

const today = "Friday";
setTimeout(() => {
  console.log(today);
}, 1000);

上面这段代码中,打印today变量的值就是异步任务,js解释到consol.log(today)这句代码时并不会马上执行这个语句,而是通知其他浏览器中其他线程计时。执行完setTimeOut后,脚本中没有其他语句,js线程空闲,与此同时当约定的1000ms到达后,浏览器中计时线程会通知js线程一个任务,即console.log(today),此时主线程空闲,于是执行这个任务。
事实上,js运行环境中有一个任务队列,当js执行完主线程后,就会执行队列中的所有任务直到清空。任务队列中有宏任务和微任务,setTimeout接收的回调是宏任务,而Promise.then接收的回调为微任务中。

console.log("1");
setTimeout(() => {
  console.log("2");
}, 0);
Promise.resolve().then(() => {
  console.log("3");
});
console.log("4");

这段代码会先打印1,然后执行setTimeout,添加计时器,由于设定的时间为0,此时以满足给定时间,于是consol.log(‘2’)这一任务被添加到宏任务中。执行到Promise语句时,Promise.resolve()返回Promise自身,调用then方法,由于此时Promise已经调用过resolve,于是then方法中console.log(“3”)加入到微任务中(Promise的resolve被调用后会将then()中传入的回调加入到微任务队列中),接着执行最后一句,打印4。至此主线程执行完成,这就是事件循环中的第一个tick。然后进入第二个tick,js线程检查微任务队列是否清空,发现有打印3的任务,于是打印3。接着,检查宏任务队列是否清空,发现有打印2的任务,于是打印2。最终打印出1 4 3 2。如果队列中还有任务,js会进入下一个tick,继续检查清空微任务,检查清空宏任务,检查清空微任务队列…以此类推。
一个更复杂的示例如下:

console.log("1");
setTimeout(() => {
  console.log("2");
  setTimeout(() => {
    console.log("3");
  }, 0);
  Promise.resolve().then(() => {
    console.log("4");
  });
}, 0);
Promise.resolve().then(() => {
  console.log("5");
  Promise.resolve().then(() => {
    console.log("6");
  });
  setTimeout(() => {
    console.log("7");
  }, 0);
});
console.log("8");
;

运行代码发现,在任务队列的一轮tick中,在检查微任务的阶段中,若有新的微任务入队,则本轮一并执行新入队的微任务,如果有新的宏任务入队,则在下一轮tick执行;而在检查宏任务的阶段中,若有新的任务入队,不管是宏任务还是微任务,都在下一轮tick执行。
回看上面的代码,首先,主线程打印1和8;然后进入下一个tick,此时执行任务队列中打印5的微任务,打印5,而在执行这个微任务的过程中还会将打印6的微任务入队,打印7的宏任务进入下一tick队列,接着由于有新加入的打印6的微任务,于是一并执行打印6;接着检查本轮宏任务,执行队列中打印2的宏任务,打印2,执行过程中打印3的宏任务和打印4的微任务进入下一轮tick队列;进入下一轮tick,队列中有微任务打印4,宏任务打印3和打印7,且打印7的宏任务比打印3的宏任务先入队,于是第二轮tick打印4 7 3.最终输出序列为1 8 5 6 2 4 7 3。
process.nextTick会在事件循环中添加新任务,可以简单理解为添加微任务,但在node文档里,“process.nextTick 回调添加到 process.nextTick queue。 Promise.then() 回调添加到 promises microtask queue。 macrotask queue 添加了 setTimeout、setImmediate 回调。”如果在一个任务中先添加了Promise.then()任务,再添加了process.nextTick()任务,process.nextTick()会优先于Promise.then()执行。上面的代码改一改

console.log("1");
setTimeout(() => {
  console.log("2");
  Promise.resolve().then(() => {
    console.log("9");
  });
  setTimeout(() => {
    console.log("3");
  }, 0);
  process.nextTick(() => {
    console.log("4");
  });
}, 0);
Promise.resolve().then(() => {
  console.log("5");
  Promise.resolve().then(() => {
    console.log("6");
  });
  setTimeout(() => {
    console.log("7");
  }, 0);
});
console.log("8");

最终打印185624973,在同一个任务执行过程中,Promise.then()打印9的任务和process.nextTick()打印4的任务先后添加进队列中,结果是打印4的任务先于打印9的任务执行。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值