理解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實現

參考

阮一峯異步編程

相關文章
相關標籤/搜索