JavaScript异步编程

同步与异步模式

js最初是设计使用在浏览器上的脚本语言,由于需要对DOM进行操作,因此是单线程的执行语言。

同步模式

  • 非同步执行而是排队执行
  • 变量或函数的声明不会产生任何的调用;
  • js在执行引擎当中维护了一个正在工作(执行)的工作表,里面记录当前的执行任务,当工作表中所有的任务被清空,这一轮的工作结束;
  • 排队执行会存在如果遇到耗时多的任务,那么后面的任务就会被延迟执行->阻塞。

异步模式

// 异步举例
console.log('global begin')

setTimeout(function timer1() {
    console.log('timer1 invoked')
}, 1800)

setTimeout(function timer2() {
    console.log('timer2 invoked')

    setTimeout(function inner() {
        console.log('inner invoked')
    }, 1000);
}, 1000);

console.log('global end')

// global begin
// global end
// timer2 invoked
// timer1 invoked
// inner invoked
  • 如果没有异步模式,单线程的js无法同时处理大量的耗时任务;
  • 难点:代码的执行顺序混乱;
  • 下达这个任务开启的指令然后继续往下执行,不会等待任务结束。

js实现异步编程的4种方法:
4种解决方式的根本都是利用了浏览器定时器的工作原理。

  1. 回调函数
    异步编程最基本的方法。
    优点:简单易理解。
    缺点:不利于代码阅读和维护,各部分之间高度耦合,流程会很混乱,而且每个任务只能指定一个回调函数。

  2. 事件监听
    采用事件驱动模式,任务执行不取决于代码的执行顺序,而是某个事件是否发生。
    优点:易理解,可以绑定多个事件,每个事件可以绑定多个回调函数,能够“去耦合”,有利于实现模块化。
    缺点:整个程序变成事件驱动型,运行流程不清晰。

  3. 发布/订阅

假设存在一个“信号中心”,某个任务执行完成,就向信号中心“发布(publish)”一个信号,其他任务可以向信号中心“订阅(subscribe)”这个信号,从而知道自己什么时候开始执行任务,这就是“发布/订阅模式”,也称“观察者模式”。

这种方法的性质与事件监听类似,但是可以通过“信号中心”查看,了解有多少信号、每个信号有多少订阅者,从而监控程序的运行。

  1. Promise对象

回调函数

由调用者定义,交给执行者执行的函数。

事件循环与消息队列

js引擎线程会维护一个执行栈(调用栈call stack),同步代码会依次加入执行栈并执行,结束会退出执行栈。
js引擎线程如果遇到异步(DOM事件监听、网络请求、setTimeout计时器等),会交给单独的线程(⚠️Web APIs)维护异步任务,直到满足一定条件(用户点击DOM、网络请求成功、计时器结束),由事件触发线程将异步对应的回调函数封装成任务并加入消息队列。
如果执行栈为,事件循环就会启动,从消息队列中取出一个任务(即异步的回调函数)放入执行栈中执行。

  • 事件循环

在线程运行过程中,接收并执行新的任务,

  • 消息队列

消息队列是一种数据结构,可以存放要执行的任务,类似于待办事件列表。

异步编程的几种方式

Promise异步方案、宏任务/微任务队列

Promise异步方案

Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值。

Promise有三个状态:
1.pending([待定]初始状态)
2.fulfilled([实现]操作成功)
3.rejected([被否决]操作失败)

当Promise状态发生改变,就会触发.then()里的响应函数处理后续步骤,由于.then().catch()方法返回的是一个新的Promise对象,因此它们可以被链式调用。

  • Promise基本用法
// Promise 基本实例
const promise = new Promise((resolve, reject) => {
    // 这里用于“兑现”承诺
    resolve('100')  // 承诺达成
    reject(new Error('promise rejected!'))  // 承诺失败
})

promise.then((value) => {
    console.log('resolved', value)
}, (error) => {
    console.log('rejected', error)
})
  • Promise使用案例
// Promise方式的AJAX
function ajax(url) {
    return new Promise((resolve, reject) => {
        var xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.onload = function () {
            if (this.status === 200) {
                resolve(this.response)
            } else {
                reject(this.statusText)
            }
        }
        xhr.send()
    })
}
  • Promise常见误区与链式调用
    嵌套使用的方式是使用Promise最常见的错误,应该经尽可能保证异步任务的扁平化。
    链式调用:
    每一个.then()方法实际上都是为上一个.then()返回的Promise对象添加状态明确过后的回调。通过链式调用避免回调的嵌套。

  • Promise异常处理
    最好使用catch明确捕获每一个异常。

  • Promise静态方法

    • Promise.resolve():快速地把一个值转换成Promise对象;如果包裹一个Promise对象,那么该Promise对象会被原样返回;还可以传入一个有then方法的Promise对象,一般用于将第三方库的Promise对象转换为原生的Promise对象
    • Promise.reject():快速地创建一个失败的Promise对象
  • Promise并行执行
    同步执行多个Promise的方式:

    • Promise.all():接收一个包含Promise对象的数组,将其中的Promise对象看作一个个异步任务,返回一个全新的Promise对象,等待所有的任务结束
    • Promise.race():只会等待第一个任务结束
宏任务/微任务队列

回调队列中的任务称之为「宏任务」

事件循环作为任务驱动的主线程,首先执行完调用栈上当前的宏任务(同步任务),然后再遍历微任务队列,把微任务队列上所有任务都执行完毕(清空微任务队列)(微任务也可以往微任务队列中添加微任务),接着渲染线程,最后从宏任务队列中取一个任务,进入下一个消息循环。

宏任务执行过程中可以临时加上一些额外需求,对于额外需求可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的「微任务」,直接在当前任务结束过后立即执行,而非到队伍末尾重新排队。
Promise的回调会作为微任务执行,setTimeout以宏任务的形式进入队列末尾。
微任务的提出是为了提高整体的响应能力。
目前绝大多是异步调用都是作为宏任务执行,Promise&MutationObserver、process.nextTick会作为微任务执行。

  • 产生宏任务的方式

    • script中的代码块
    • setTimeout()
    • setInterval()
    • setImmediate()(非标准、IE和Node.js中支持)
    • 注册事件
  • 产生微任务的方式

    • Promise
    • MutationObserver
    • queueMicrotask()
  • 何时使用微任务
    微任务执行的时机,晚于当前本轮事件循环的Call Stack(调用栈)中的代码(宏任务),早于时间处理函数和定时函数。
    使用微任务的最主要原因简单归纳为:
    1.减少操作中用户可感知到的延迟(微任务中操作dom之后立即渲染);
    2.确保任务顺序的一致性,即便是结果或数据是同步可用的;
    3.批量操作的优化。

Generator异步方案、Async/Await语法糖

Generator异步方案

Generator函数是一个封装的异步任务,或者说是异步任务的容器。

  • generator由function *定义,不同于普通函数,可以暂停执行;
  • 异步操作需要暂停的地方,用yield语句注明;
  • 调用next()执行generator函数,从上次返回的yield语句处继续执行。
Async/Await语法糖

语言层面的异步编程标准。async函数返回一个Promise对象,await等待接收async函数的返回值。是Generator的语法糖。

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

推荐阅读更多精彩内容

  • 还记得一年前写过一篇关于JavaScript异步编程简述的文章,主要介绍了JavaScript的单线程特性与异步编...
    极乐君阅读 389评论 1 7
  • 同步模式与异步模式 事件循环与消息队列   JavaScript 单线程指的是浏览器中负责解释和执行 JavaSc...
    Dear丶BQ阅读 361评论 0 0
  • Event Loop 可以看出: Promise和setTimeout都是是异步 Promise优先级高于setT...
    夏末远歌阅读 300评论 0 0
  • 所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后转而执行其他任务,当第一段有了执行结果之后,再回过头执...
    DJL箫氏阅读 432评论 0 2
  • 同步和异步 同步编程,就是计算机一行一行按照顺序依次执行代码,当前代码任务耗时执行会阻塞后续代码的执行。 同步编辑...
    vuturn阅读 272评论 0 0