js 异步

浏览器(或者说JS引擎)执行JS的机制是基于事件循环。

由于JS是单线程,所以同一时间能且只能执行一个任务,其他任务就得排队,后续任务必须等到前一个任务结束才能开始执行。

为了避免因为某些长时间任务造成的无意义等待,JS引入了异步的概念,用另一个线程来管理异步任务。

同步任务直接在主线程队列中顺序执行,而异步任务会进入另一个任务队列,不会阻塞主线程。等到主线程队列空了(执行完了)的时候,就会去异步队列查询是否有可执行的异步任务了(异步任务通常进入异步队列之后还要等一些条件才能执行,如ajax请求、文件读写),如果某个异步任务可以执行了便加入主线程队列,以此循环。

先来看一下实现异步的一些方式,下面用分类的方式列举一下
  1. 经典的回调函数
  • callback
  1. 监听事件
  • onload
  • new Image
function asynByImg( callback ) {
  var img = new Image();
  img.onload = img.onerror = img.onreadystatechange = function() {
    img = img.onload = img.onerror = img.onreadystatechange = null;
    callback(); 
  }
  img.src = "data:image/png,";
}
asynByImg(function(){
  console.log(1);
});
console.log(2);
  • 事件绑定
  • 发布/订阅模式
  • Message
  1. 延迟类
  • setTimeout
setTimeout(function() {
  console.log(1);
});
console.log(2);
  • setInterval
setInterval(function() {
  console.log(1);
});
console.log(2);
  • requestAnimationFrame
(function() {
  var requestAnimationFrame = 
    window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame;
  var startTime = window.mozAnimationStartTime || Date.now();
  requestAnimationFrame(draw);
  function draw (timestamp) {
    // 计算两次重绘的时间间隔
    var drawStart = timestamp || Date.now();
    var diff = drawStart - startTime;
    startTime = drawStart;
    requestAnimationFrame(draw);
  }
})()
  • setImmediate
  • process.nextTick()
  1. 异步功能函数
  • promise
  • ajax
  • async / await
  • Worker
  • Co / Generator
这些方式的执行顺序是什么样的?为什么?

先看下面的示例:

console.log('start')

const interval = setInterval(() => {  
  console.log('setInterval')
}, 0)

setTimeout(() => {  
  console.log('setTimeout 1')
  Promise.resolve()
      .then(() => {
        console.log('promise 3')
      })
      .then(() => {
        console.log('promise 4')
      })
      .then(() => {
        setTimeout(() => {
          console.log('setTimeout 2')
          Promise.resolve()
              .then(() => {
                console.log('promise 5')
              })
              .then(() => {
                console.log('promise 6')
              })
              .then(() => {
                clearInterval(interval)
              })
        }, 0)
      })
}, 0)

Promise.resolve()
    .then(() => {  
        console.log('promise 1')
    })
    .then(() => {
        console.log('promise 2')
    })

上面的执行结果依次打印下面的内容

start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6
setInterval
。。。

想要知道上面的原理需要先来了解下面的几个内容

  1. 事件循环
    JavaScript在一个时间仅处理一个任务. 就是JavaScript在执行时, 存在一个执行队列, 依次执行队列中的任务, 不能同时执行多个任务。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)关于事件循环详细说明可以查看这里
    这个过程用一个现实的例子就像是去银行只有一个窗口,办理业务就要排队,好痛苦。但是如果一个任务需要执行很久很久,肿么办?等着呗。但是聪明的程序员怎么会忍受一个任务执行那么长时间,于是把任务分成了macrotasks和microtasks两类。
  2. macrotasks和microtasks
    macrotasks【同步任务】: script(整体代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
    microtasks【异步任务】: process.nextTick, Promises, Object.observe, MutationObserver
    同步任务以及异步任务会进入不同的“场所”,同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程执行完毕为空,就会去Event Queue读取对应的函数,进入主线程执行。

然后解析上面代码的执行:
1. 同步任务直接放入到主模块(主线程)任务队列执行. 异步任务挂起后台执行, 等待IO事件完成或行为事件被触发.
2. 系统后台执行异步任务, 如果某个异步任务事件发生(或者是行为事件被触发), 则将该任务push到任务队列中, 每个任务会对应一个回调函数进行处理. 这个步骤在后台一直执行, 因为就不断有事件被触发, IO不断完成, 任务被不断的加入到任务队列中.
3. 执行任务队列中的任务. 任务的具体执行是在执行栈中完成的. 当运行栈中一个任务的基本运行单元(称之为Frame, 桢)全部执行完毕后, 去读取任务队列中的下一个任务, 继续执行. 是一个循环的过程. 处理一个任务队列中的任务, 称之为一个tick.

即 macrotasks执行console.log('start') 执行后打印了‘start’; 然后macrotasks完成进入microtasks,执行了console.log('promise 1')console.log('promise 2');microtasks执行完成返回查看macrotasks执行了console.log('setInterval')console.log('setTimeout 1'); microtasks执行console.log('promise 3')console.log('promise 4'); 再次进入macrotasksconsole.log('setInterval')console.log('setTimeout 2');最后执行microtasks队列console.log('promise 5')console.log('promise 6'),到这里microtasks全部完成,后面执行macrotasks

备注:事件队列是遵循先进先出, 需要依次处理. 所以, 定时器函数运行时,如果遇到了定时的事件, 事件发生, 也仅仅是将该任务push入任务队列而已(并没有立即执行回调函数)。同时这也是为什么setTimeout预定的时间不一定会执行,例如你预定300ms后执行,它最早是300ms的原因

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342