javascript 異步編程

javascript 是單線程執行的,由js文件自上而下依次執行。即爲同步執行,如果有網絡請求或者定時器等業務時,不能讓瀏覽器傻傻等待到結束後再繼續執行後面的js吧!因此js設計了異步模式!javascript

下面是一個常見的定時器與promise的問題:java

setTimeout(() => {
    console.log('我是第一個宏任務');
    Promise.resolve().then(() => {
        console.log('我是第一個宏任務裏的第一個微任務');
    });
    Promise.resolve().then(() => {
        console.log('我是第一個宏任務裏的第二個微任務');
    });
}, 0);

setTimeout(() => {
    console.log('我是第二個宏任務');
}, 0);

Promise.resolve().then(() => {
    console.log('我是第一個微任務');
});

console.log('執行同步任務');

執行結果以下:promise

clipboard.png

爲何是這種執行結果?瀏覽器

這就要說到js的執行機制:事件循環(event loop)!網絡

當JS解析執行時,會被引擎分爲兩類任務,同步任務(synchronous)異步任務(asynchronous)異步

對於同步任務來講,會被推到執行棧按順序去執行這些任務。
對於異步任務來講,當其能夠被執行時,會被放到一個 任務隊列(task queue) 裏等待JS引擎去執行。async

當執行棧中的全部同步任務完成後,JS引擎纔會去任務隊列裏查看是否有任務存在,並將任務放到執行棧中去執行,執行完了又會去任務隊列裏查看是否有已經能夠執行的任務。這種循環檢查的機制,就叫作事件循環(Event Loop)。函數

clipboard.png

對於任務隊列,實際上是有更細的分類。其被分爲 微任務(microtask)隊列 & 宏任務(macrotask)隊列
宏任務: setTimeout、setInterval等,會被放在宏任務(macrotask)隊列。
微任務: Promise的then、Mutation Observer等,會被放在微任務(microtask)隊列。
1.首先執行執行棧裏的任務。
2.執行棧清空後,檢查微任務(microtask)隊列,將可執行的微任務所有執行。
3.取宏任務(macrotask)隊列中的第一項執行。
4.回到第二步。oop

如今咱們知道了爲何定時器會晚於promise執行了。下面咱們討論一下微任務的幾種實現狀況:Promsie、Generator、async/awaitspa

===Promsie===

Promise對象是一個構造函數,用來生成Promise實例;

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 異步操做成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise 新建後就會當即執行。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

跟傳統的try/catch代碼塊不一樣的是,若是沒有使用catch方法指定錯誤處理的回調函數,Promise 對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。

===Generator===

Generator 函數是一個狀態機,封裝了多個內部狀態。
執行 Generator 函數會返回一個遍歷器對象,也就是說,Generator 函數除了狀態機,仍是一個遍歷器對象生成函數。返回的遍歷器對象,能夠依次遍歷 Generator 函數內部的每個狀態。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

===async===

async 函數是什麼?一句話,它就是 Generator 函數的語法糖。

(1)內置執行器。

Generator 函數的執行必須靠執行器,因此纔有了co模塊,而async函數自帶執行器。也就是說,async函數的執行,與普通函數如出一轍,只要一行。

asyncReadFile();
上面的代碼調用了asyncReadFile函數,而後它就會自動執行,輸出最後結果。這徹底不像 Generator 函數,須要調用next方法,或者用co模塊,才能真正執行,獲得最後結果。

(2)更好的語義。

async和await,比起星號和yield,語義更清楚了。async表示函數裏有異步操做,await表示緊跟在後面的表達式須要等待結果。

(3)更廣的適用性。

co模塊約定,yield命令後面只能是 Thunk 函數或 Promise 對象,而async函數的await命令後面,能夠是 Promise 對象和原始類型的值(數值、字符串和布爾值,但這時會自動轉成當即 resolved 的 Promise 對象)。

(4)返回值是 Promise。

async函數的返回值是 Promise 對象,這比 Generator 函數的返回值是 Iterator 對象方便多了。你能夠用then方法指定下一步的操做。

進一步說,async函數徹底能夠看做多個異步操做,包裝成的一個 Promise 對象,而await命令就是內部then命令的語法糖。

相關文章
相關標籤/搜索