JavaScript 异步编程

JavaScript 异步编程

内容概要

  • 同步模式与异步模式
  • 事件循环与消息队列
  • 异步编程的几种方式
  • Promise异步方案、宏任务/微任务队列
  • Generator异步方案、Async / Await 语法糖

1.0 同步模式与异步模式

1.1 进程和线程

​ 进程:当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。

​ 线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。

1.2 单线程和多线程

生活举例:早上来到公司,正准备打卡,来电话了。

​单线程:如果你先接完电话,再打卡,就是单线程。

​ 多线程:如果你一手接电话,一手打卡,就是多线程。

1.3 采用单线程模式工作的原因

为了实现页面上的动态效果,否则就会出现页面上的多线程同步等问题…

例:某一个线程添加了某个dom元素,另一个线程删除了此dom元素,那么浏览器就无法正常显示。

为了避免这种线程同步问题,javascript在开始的时候就避免了这个问题:

Js执行环境中负责执行代码的线程只有一个(例:一个人就相当于一个线程,一个人只能做一个工作,如果有多个工作那么就只能排队,依次完成,)

这种模式的优点:更安全更简单

缺点:如果我们遇到一个特别耗时的任务,那后面的任务就必须等待这个任务的结束。整个程序执行会被拖延,出现假死的情况。

JavaScript将任务的执行模式分成了两种:同步模式(Synchronous)异步模式(Asynchronous)

1.4 同步模式

"同步模式"就是后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。

同步模式下的执行机制 :

js在执行引擎当中维护了一个正在执行的工作表,在这里面会记录当前我们正在做的一些事情,当工作表当中这些事情全部结束以后,那么这一轮工作也就算结束了。那么如果其中一件事情中某一行代码的执行之间过长,就会阻塞下面代码的执行,时间一长的话,给用户直观地体验就是页面会有卡顿,甚至卡死的现象

//同步模式 - 执行顺序
console.log('global begin') // 1
function bar(){ //遇到两个函数并不会执行
    console.log('bar task') // 5
}
function foo(){
    console.log('foo task') // 3
    bar() // 4
}
foo() // 2 函数调用
console.log('global end') // 6

//控制台打印
global begin
foo task
bar task
global end

1.5 异步模式

"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。*

单线程的JavaScript语言就无法同时处理大量耗时任务 --> 异步模式代码的执行顺序比较混乱。

异步模式下的执行机制:

跟同步模式不同的是,异步模式下,是不会去等待这个任务的结束才开始下一个任务,而是在异步开启后,立即执行下一个任务,不会阻塞下面的异步代码。

异步模式对于javascript语言来说是非常重要的。

如果没有异步模式的话,单线程的javascript语言就无法同时处理大量耗时任务。

console.log('global begin') // 1
setTimeout(function time1(){ // 2 开启倒计时后 放入Web APIs 对于setTimeout来说调用就结束了,继续往后执行
    console.log('time1 invoke') // 7
},1800)
setTimeout(function time2(){ // 3 同2
    console.log('time2 invoke') // 5
    setTimeout(function inner(){ // 6
        console.log('inner invoke') // 8
    },1000)
},1000)
console.log('global end') // 4
//此时 调用栈清空
//Event loop发挥作用:监听调用栈Call stack和消息队列Queue
//Web APIs中 time2()的倒计时先结束后,放入Queue,time1()同理。
//消息队列将第一个time2()取出放入调用栈,inner()放入Web APIs
//time1()放入调用栈执行。
//inner()执行。
//控制台输出
global begin
global end
time2 invoke
time1 invoke
inner invoke

2.0 事件循环与消息队列

在这里插入图片描述

2.1 消息队列

消息队列其实是一种数据结构,存放主线程中遇到的异步任务,符合“先进先出”的特点,即添加任务往尾部添加,取出任务从头部取。

2.2 事件循环

在线程运行过程中,接受并执行新的任务,就需要采用事件循环机制。

事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,关键步骤如下:

  • 在此次 tick 中选择最先进入队列的任务(oldest task),如果有则执行(一次)

  • 检查是否存在 Microtasks,如果存在则不停地执行,直至清空 Microtasks Queue

  • 更新 render

  • 主线程重复执行上述步骤

注:

  • JS分为同步任务和异步任务
  • 同步任务都在主线程上执行,形成一个执行栈
  • 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

3.0 异步编程的几种方式

3.1 回调函数 — 所有异步编程方案的根基

回调函数可以理解为一件你想要做的事情

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

function foo(callback){
    setTimeout(function(){
        callback()
    },3000)
}

foo(function(){
    console.log('这就是一个回调函数')
    console.log('调用者定义这个函数,执行者执行这个函数')
    console.log('其实就是调用者告诉执行者异步任务结束后应该做什么')
})

优缺点:回调函数易于实现、便于理解,但是多次回调会导致代码高度耦合

3.2 事件监听

$(document).ready(function(){
    console.log('DOM is ready')
})

脚本的执行不取决代码的顺序,而取决于某一个事件是否发生

3.3 发布订阅模式

//订阅done事件
$('#app').on('done',function(data){
    console.log(data)
})
//发布事件
$('#app').trigger('done,'123')

发布/订阅模式是利用一个消息中心,发布者发布一个消息给消息中心,订阅者从消息中心订阅该消息,。类似于 vue 的父子组件之间的传值。

3.4 Promise — 一种更优的异步编程统一方案

export default function getMethods (url){
    return new Promise(function(resolve, reject){
        axios.get(url).then(res => {
            resolve(res)
        }).catch(err =>{
            reject(err)
        })
    })
}

getMethods('/api/xxx').then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

3.5 Generator

function * foo(){
    console.log('start')
    try{
        const res = yield 'foo'
    }catch(e){
        console.log(e)
    }
}
const generator = foo()
const result = generator.next()
console.log(result)
// generator.next('bar')
generator.throw(new Error('Generator error'))

3.6 async

async function demo() {
  try {
    await new Promise(function (resolve, reject) {
      // something
    });
  } catch (err) {
    console.log(err);
  }
}

demo().then(data => {
    console.log(data)  // 
})

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

4.1 Promise异步方案

Promise是异步编程的解决方案,比传统的回调函数解决方式更加合理更加强大更加优雅。

Promise的特性:1.对象状态不受外界影响 2.一旦状态改变,就不会再发生变化

//Promise的使用
const promise = new Promise((resolve, reject) => {
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

4.2 宏任务/微任务队列

宏任务

(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染。

宏任务包含:

script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)
微任务

microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。

所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

微任务包含:

Promise.then
Object.observe
MutaionObserver
process.nextTick(Node.js 环境)
运行机制

在这里插入图片描述

在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

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

5.1 Generator异步方案

虽然 Promise 能够利用链式调用的特性来有效缓解回调函数嵌套调用的问题,但归根到底 Promise 仍然没有办法达到传统同步代码的可读性。

//Generator 语法与简单使用
function * foo () {
	console.log('start')
  const res = yield 'foo'
  console.log(res)
}
const generator = foo()

const result = generator.next()
console.log(result)

generator.next('bar')

generator.throw(new Error('Generator error'))

5.2 Async / Await 语法糖

Async / Await 的优点

  1. 方便级联调用

  2. 同步代码编写方式

  3. 多个参数传递

  4. 同步代码和异步代码可以一起编写

  5. 基于协程

  6. async/await是对Promise的优化

//Async 返回值是一个 Promise 对象
//Await 只能出现在 Async 函数内部
async function async1() {
    return new Promise(resolve => {
        setTimeout(() => resolve("async1"), 2000);
    });
}

async function async2() {
    const result = await async1();
    console.log(result);
}
async2();
console.log('end');
  1. async/await是对Promise的优化
//Async 返回值是一个 Promise 对象
//Await 只能出现在 Async 函数内部
async function async1() {
    return new Promise(resolve => {
        setTimeout(() => resolve("async1"), 2000);
    });
}

async function async2() {
    const result = await async1();
    console.log(result);
}
async2();
console.log('end');
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值