理解js中的异步编程

JS异步编程模型

在理解js异步编程时, 咱们先再心中想一下为何js语言会引入异步任务?异步到底解决了哪些问题?理解了这些以后,咱们才能更好地运行异步编程思想去书写咱们的业务代码逻辑。。。下面写一下我的对异步模型的理解javascript

JS中的任务

所谓js中的任务,通俗点咱们能够理解为等待运行的js代码(这里不搞那些专业术语),到此咱们能够分为顺序当即执行的代码(同步任务),以及非当即顺序执行的代码(异步任务)。html

  • 两种任务分析
同步任务有个特色,就是顺序执行,代码被编译解析后按照既定的顺序去一步一步执行,
这种执行方式效率高吗?视状况而定。
若是碰到一串耗时代码,意味着此代码段后面的代码须要等待该代码执行完毕他才能执行,
这当然是不行的(代码运行被堵塞了);

因此此时便引入异步的概念,咱们把这段耗时任务扔给其余执行器(或者说线程)去处理,咱们
只须要获取其余执行器处理后的结果,让结果代码滞后执行,或者说到相应的时机去执行(怎么去判断时机,发布订阅,先不说了)
让主线程继续执行其同步任务,这样效率是否是提升了,至少不会发生代码堵塞的问题了吧 :)
  • js中异步任务
引入异步任务是为了提升代码执行的效率和速度,我以为这只是结果的一部分。 为何呢?

我的理解仍是js这门语言的缺陷,js做为一种单线程语言,意味着它在处理 多任务并发时 没有了多线程语言(如java)的优点,
一个主线程下代码只得一行行执行咯;cpu的多核能力也不能彻底发挥啊,emm...
因此异步任务的引入 必定程度上也提高了js在处理多任务的能力吧。

其实吧,js中异步任务(如网络请求,定时器, 事件监听等)是浏览器的其余进程/线程 为js主线程分担了处理多任务的压力,
浏览器其余进程/线程将异步任务处理后结果扔到js的事件循环机制的任务队列里,那么这里必然涉及到
进程/线程间的通讯,必定程度上也是会损耗部分效率的
  • 因此为何引入异步?异步解决的问题?
宏观上来讲:
    提高js代码执行效率, 怎么就提升了? 思考一下
    提高js处理多任务的能力, 怎么去提高? 思考一下

js如何实现异步机制

前面提到异步任务的概念:非当即执行的代码, 固然是不彻底准确的啦,
js中的异步任务会被放到任务队列(task queue)的任务,经过js事件循环机制(其实就是js主线程一直轮询访问任务队列);
"任务队列"会通知js主线程,当某个异步任务能够执行了,该任务才会进入主线程的执行栈执行;vue

因此异步任务的特色之一是存在一种等待状态,滞后执行;那么js怎么来实现异步模式呢?java

执行栈 + 任务队列

那么js中到底哪些才是异步任务呢? 有具体规范吗?没找到明确的规范
我的理解:凡是被放到事件队列里的任务就是异步任务,这些任务与运行环境相关
而执行栈只是做为任务的消费者而已,真正生产异步任务的生产者是:浏览器那些DOM API,网络线程,计时器线程; node环境下的事件等。。。node

js实现异步编程的方式

异步编程为了啥?固然是为了更快的执行代码任务啊,怎么作?那代码为何慢呢?任务太多了呀,因此咱们要对任务进行合理拆分git

1.回调函数

f1(); // f1为耗时任务
f2(); // f2依赖f1的结果

function f1(cb) {
    setTimeout(() => {
        // f1 的逻辑代码
        // 。。。
        cb()
    })
}
f1(f2) // f1被转化为异步任务,f2在它以后执行

回调的方式代码耦合性太强,也不能捕获异常try catchgithub

2.事件监听

事件监听的本质在于 事件状态驱动,触发回调, 把阮老师的demo实现了一下编程

const EVENT = {};

Function.prototype.on = function (eventName, cb) {
  EVENT[eventName] = cb;
};

Function.prototype.trigger = function (eventName) {
  EVENT[eventName]();
};

function f1() {
  setTimeout(() => {
    console.log("f1 start");
    // 触发事件
    f1.trigger('done')
  }, 1000);
}

function f2() {
  console.log("f2");
}

f1.on("done", f2); // 添加监听
f1()

实现了功能解耦,其实仍是依靠回调函数, 只不过触发方式变化了, 不是直接嵌套在上一步任务里执行了promise

3.发布订阅

发布订阅基于事件监听,发布者和订阅者经过一个事件中心进行通讯, 而且实现了多个事件解耦浏览器

/**
 * 发布订阅方式
 * 维护一个事件中心进行通讯
 */

const event = {
  // 事件中心
  eventList: [],

  // 订阅事件, 添加一个回调逻辑
  on(type, fn) {
    if (!this.eventList[type]) {
      this.eventList[type] = [];
    }
    this.eventList[type].push(fn);
  },

  // 发布事件, 遍历事件列表,去执行全部事件
  emit(type, ...args) {
    const cbList = this.eventList[type];
    if (!cbList || cbList.length === 0) return;

    cbList.forEach((cb) => {
      cb.apply(event, args);
    });
  },
};

let data = {};

// 咱们能够订阅多个事件, 而且相比回调, 订阅结合发布彻底解耦了, 二者并没有关联性
event.on("change", (data) => {
  // 订阅者1的逻辑
  console.log("订阅者1: data obj change", data);
});

event.on("change", (data) => {
  // 订阅者2的逻辑
  if (Object.keys(data).length === 2) {
    console.log('订阅者2: data s数据有两个了', data)
  }
});

// 发布事件: 咱们能够等待数据状态发生变化或者 异步执行完去发布
setTimeout(() => {
  data.name = 'huhua'
  // 发布者, 我想在哪发就在哪发
  event.emit('change', data)
}, 1000);

setTimeout(() => {
  data.age = '26'
  event.emit('change', data)
}, 2000);
既然说到了发布订阅, 咱们顺便理解一下观察者模式

vue源码中不是用到了吗...那咱们动手写写, 看看发布订阅和观察者模式的区别

/**
 * 观察者模式的简易实现
 * 观察者对象:   须要在被观察者状态变化时触发更新逻辑
 * 被观察者对象: 须要收集全部的对本身进行观测的观察者对象
 */

// 被观察者
// 对于一个被观察的人来讲: 我要知道是哪些人在观察我, 个人状态怎么样
class Sub {
  constructor(name) {
    this.name = name;
    this.state = "pending";
    this.observer = []; // 存放全部观察者的集合
  }

  // 添加观察者
  add(ob) {
    this.observer.push(ob);
  }
  // 更改状态
  setState(newState) {
    this.state = newState;
    // 状态改了不起告诉全部观察者啊, 其实就是执行观察者对象的更新函数
    this.notify();
  }
  // 通知
  notify() {
    this.observer.forEach((ob) => ob && ob.update(this));
  }
}

// 观察者
class Observer {
  constructor(name) {
    this.name = name;
  }

  update(sub) {
    console.log(
      `观察者${this.name} 已收到被观察者${sub.name}状态改变了: ${sub.state}`
    );
  }
}

let sub = new Sub('学生小麦')

let ob1 = new Observer('语文老师')
let ob2 = new Observer('数学老师')
let ob3 = new Observer('英语老师')

// 与发布订阅不一样的是, 这里被观察者须要添加全部的观察者对象, 以便在本身状态改变时去执行观察者的更新逻辑
// 两者有关联关系, 我要知道我被谁观察

// 发布订阅中, 发布者和订阅者之间没有关联关系, 经过事件中心来管理
// 订阅不须要知道谁去发布
sub.add(ob1)
sub.add(ob2)
sub.add(ob3)

sub.setState('fulfilled')
// 观察者语文老师 已收到被观察者学生小麦状态改变了: fulfilled
// 观察者数学老师 已收到被观察者学生小麦状态改变了: fulfilled
// 观察者英语老师 已收到被观察者学生小麦状态改变了: fulfilled

sub.setState('rejected')
// 观察者语文老师 已收到被观察者学生小麦状态改变了: rejected
// 观察者数学老师 已收到被观察者学生小麦状态改变了: rejected
// 观察者英语老师 已收到被观察者学生小麦状态改变了: rejected

4.Promise对象

promise为咱们提供了一种新的异步编程方式, 写这篇文章目的也是为了手动实现一个知足A+规范的promise对象;
咱们先来看一看promise A+规范 https://www.ituring.com.cn/ar...

其实promise的核心就是then方法, 源码中也用到发布订阅模式思想, 经过then链的 链式回调将上一步结果透传给下一步使用(返回了一个新的promise),
解决了回调地狱的问题

手写promise正在进行中ing...到时候附上连接

5.async + await

async + await是最新的异步编程方案...比较符合咱们的编码习惯

其实搞懂了generator函数和promise以后, async和await就很好懂了, 我后面也实现了一遍
附上连接:
https://github.com/appleguard...

仍是说一下:
generator函数是一个状态机,封装了多个内部状态
generator函数除了状态机,仍是一个遍历器对象生成函数
可暂停函数, yield可暂停(保存上下文),next方法可启动,每次返回的是yield后的表达式结果
yield表达式自己没有返回值,next方法能够带一个参数,该参数就会被看成上一个yield表达式的返回值

async+await 就是一个被包装的generator自执行器函数,结合promise实现

参考

阮一峰异步编程

相关文章
相关标签/搜索