[ES6] 异步处理

[回顾] 事件循环

JS运行的环境称之为宿主环境

执行栈:call stack,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。

function a() {
    console.log("a");
    b()
}

function b(){
    console.log("b");
    c()
}

function c(){
    console.log("c")
}

console.log("全局")
a()

// 全局 a b c
// 首先在执行栈插入全局上下文(入栈push),执行log全局,执行完就销毁;
// 接着在执行栈插入a的上下文(入栈push),然后执行log(a),执行完销毁;
// 接着在执行栈插入b的上下文(入栈push),然后执行log(b),执行完销毁;
// 接着在执行栈插入c的上下文(入栈push),然后执行log(c),执行完销毁,函数C都执行完后销毁c的上下文(出栈pop);
// 销毁b的上下文(出栈pop);
// 销毁a的上下文(出栈pop);
// 最后销毁全局上下文(出栈pop)

// 先进后出,先入栈push永远在执行栈的最顶端

JS引擎永远执行的是执行栈的最顶部。

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。

console.log("a");

setTimeout(() => {
    console.log("b")
}, 0);

console.log("c")

// a c b

浏览器宿主环境中包含5个线程:

  1. JS引擎:负责执行执行栈的最顶部代码
  2. GUI线程:负责渲染页面
  3. 事件监听线程:负责监听各种事件
  4. 计时线程:负责计时
  5. 网络线程:负责网络通信

当上面的线程发生了某些事情,如果该线程发现,这件事情也有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当JS引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。

JS引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务(队列):macroTask,计时器结束的回调、事件回调、http回调等等绝大部分异步函数进入宏队列
  • 微任务(队列):MutationObserver,Promise产生的回调进入微队列。

MutationObserver用于监听某个DOM对象的变化

当执行栈清空时,JS引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。

事件和回调函数的缺陷

我们习惯于使用传统的回调或事件处理来解决异步问题

事件:某个对象的属性是一个函数,当发生某一件事时,运行该函数
回调:运行某个函数以实现某个功能的时候,传入一个函数作为参数,当发生某件事的时候,会运行该函数

本质上,事件和回调并没有本质的区别,只是把函数放置的位置不同而已。

一直以来,该模式都运作良好。

目前,该模式主要面临以下两个问题:

  1. 回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套

  2. 异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增

异步处理的通用模型

ES官方参考了大量的异步场景,总结出了一套异步的通用模型,该模型可以覆盖几乎所有的异步场景,甚至是同步场景

值得注意的是,为了兼容旧系统,ES6并不打算抛弃掉过去的做法,只是基于该模型推出一个全新的API,使用该API,会让异步处理更加的简洁优雅。

理解该API,最重要的是理解它的异步模型

  1. ES6将某一件可能发生异步操作的事情,分为两个阶段:unsettledsettled
  • unsettled:未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事

  • settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转

事情总是从未决阶段逐步发展到已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力。

  1. ES6将事情划分为三种状态:pending, resolved, rejected
  • pending:挂起,处于未决阶段,则表示这件事情还在挂起(最终结果还没出来)
  • resolved:已处理,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
  • rejected 已拒绝,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误

既然未决阶段有权利决定事情走向,因此,未决阶段可以决定事情最终的状态

我们将把事情变为resolved状态的过程叫做:resolve,推向该状态时,可能会传递一些数据

我们将把事情变为rejected状态的过程叫做:reject,推向该状态时,同样可能会传递一些数据,通常为错误信息

  1. 当事情达到已决阶段后,通常需要进行后续处理,不同的已决状态,决定了不同的后续处理
  • resolved状态:这是一个正常的已决状态,后续处理表示为thenable
  • rejected状态:这是一个非正常的已决状态,后续处理表示为catchable

后续处理可能有多个,因此会形成作业队列,这些后续处理会按照顺序,当状态到达后依次执行

Promise的基本使用

const pro = new Promise((resolve, reject)=>{
// 未决阶段的处理
// 通过调用resolve函数将Promise推向已已决阶段的resolved状态
// 通过调用reject函数将Promise推向已决阶段的rejected状态
// resolve和reject均可以传递最多一个参数,表示推向状态的数据
})

pro.then(data => {
// 这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
// 如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
// data为状态数据
}, err => {
// 这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
// 如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
// err为状态数据
})

细节

  1. 未决阶段的处理函数是同步的,会立即执行
  2. thenablecatchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
  3. pro.then可以只添加thenable函数,pro.catch可以单独添加catchable函数
  4. 在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获
  5. 一旦状态推向了已决阶段,无法再对状态做任何更改
  6. Promise并没有消除回调,只是让回调变的可控

Promise的串联

当后续的Promise需要用到之前的Promise的处理结果时,需要Promise串联

Promise对象中,无论时then方法还是catch方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:

  1. 如果当前的Promise是未决的,得到的新的Promise是挂起状态
  2. 如果当前的Promise是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为resolved状态数据,应用到新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用在新的Promise中。

后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态

Promise 的串联

原型成员(实例成员)

  • then:注册一个后续处理函数,当 Promiseresolved 状态时运行该函数
  • catch:注册一个后续处理函数,当 Promiserejected 状态运行该函数
  • finally:注册一个后续处理函数(无参),当 Promise 为已决时运行该函数

构造函数成员(静态成员)

  • resolve(数据):该方法返回一个 resolved状态的 Promise,传递的数据作为状态数据

    • 特殊情况:如果传递的数据是 Promise,则直接返回传递的 Promise 对象
  • reject(数据):该方法返回一个rejected状态的 Promise,传递的数据作为状态数据

  • all(iterable) 这个方法返回一个新的 Promise 对象,该 Promise 对象在 iterable 参数对象里所有的 Promise 对象都成功的时候才会触发成功,一旦有任何一个 iterable 里面的 Promise 对象失败则立即触发该Promise 对象的失败。这个新的 Promise 对象在触发成功状态以后,会把一个包含 iterable 里所有 Promise 返回值的数组作为成功回调的返回值,顺序跟 iterable 的顺序保持一致;如果这个新的 promise 对象触发了失败状态,它会把 iterable 里第一个触发失败的 Promise 对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个 Promise 对象的状态集合。

  • race(iterable):当 iterable 参数里的任意一个子 Promise 被成功或失败后,父 Promise 马上也会用子 Promise 的成功返回值或失败详情作为参数调用父 Promise 绑定的相应句柄,并返回该 Promise 对象。

async 和 await

asyncawait 是ES2016新增的两个关键字,它们借鉴了ES2015 中生成器的实际开发中的应用,目的是简化Promise API 的使用,并非是替代了 Promise

async

async用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置, 被修饰的函数返回结果一定是 Promise 对象。

async function test () {
    console.log(1);
    return 2
}

// 等同于

function test () {
    return new Promise((resolve, reject) => {
        console.log("promise")
        resolve(2)
    })
}

await

await关键字必须出现在async函数中

await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable中的状态数据

async function test () {
    console.log(1)
    return 2
}

async function test1 () {
    const result = await test()
    console.log(result)
}

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

推荐阅读更多精彩内容