国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Javascript關于異步編程的發展

princekin / 1878人閱讀

摘要:前言轉簡體重新排版布局代碼全面使用并且直接附上輸出結果補充細節補充內容增加例子說明新增和在遍歷情況下怎么使用上文講了關于執行機制單線程同異步任務事件循環的知識點我們知道在某一時刻內只能執行特定的一個任務并且會阻塞其它任務執行為了解決這個

前言

PS:
2018/08/08 轉簡體
2018/08/09 重新排版布局,代碼全面使用ES6并且直接附上輸出結果,補充細節
2018/08/13 補充Async..await內容,增加例子說明
2018/08/20 新增Async..await和Promise.all在遍歷情況下怎么使用

上文講了關于Javascript執行機制--單線程,同異步任務,事件循環的知識點,我們知道Javascript在某一時刻內只能執行特定的一個任務,并且會阻塞其它任務執行,為了解決這個問題,Javascript語言將任務的執行模式分成兩種:

同步(Synchronous):一定要等任務執行完了,得到結果,才執行下一個任務;

異步(Asynchronous):不等任務執行完,直接執行下一個任務,等到后面再繼續執行未完成的任務;

現在我們就講講關于異步編程的發展
更多細節請看阮一峰大神的《ECMAScript 6 入門》

總結圖

如果看不清晰右鍵圖片新標簽頁打開

回調函數 百度百科的解釋是:
回調函數就是一個通過函數指針調用的函數.如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數.回調函數不是由該函數的實現方直接調用,而是在特定的事件或條件發生時由另外的一方調用的,用于對該事件或條件進行響應.
機制:
⑴定義一個回調函數;
⑵提供函數實現的一方在初始化的時候,將回調函數的函數指針注冊給調用者;
⑶當特定的事件或條件發生的時候,調用者使用函數指針調用回調函數對事件進行處理.

例如:

//定義一個回調函數
function callback() {
  console.log("I am a callback!");
}

//函數實現
function trigger(fn, time) {
  setTimeout(function() {
    fn && fn();
  }, time || 1000);
}

//耐心等兩秒哦
trigger(callback, 2000);

//輸出
//I am a callback!
優劣點 缺點:

線性理解能力缺失,代碼高度耦合,不利于維護閱讀,嵌套層級深的情況流程混亂,陷入“回調地獄”;

因為回調本身錯誤無法被外層代碼捕捉,需要在實現內部里實現額外的容錯代碼(返回報錯,多次執行,不執行等);

tips:因為異步任務在執行機制里處理方式不同的問題,try/catch語句只能捕捉執行棧上的錯誤,詳情請回顧Javascript執行機制--單線程,同異步任務,事件循環

事件監聽

也叫觀察者模式,這是一種常見的編程方式,一個對象(目標對象和觀察者對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知.執行事件由觸發事件調用與順序無關,易用低耦合并且不依賴函數調用.

DOM監聽事件



  
    
    
  

  
    
  • item1
  • item2
  • item3
優劣點 優點:

低耦合,目標對象和觀察者對象建立了一個抽象的關系;

一對多的映射關系,目標對象的狀態發生改變,所有的觀察者對象都將得到通知,進行各自的回調操作;

任務的執行不取決于代碼的順序,而取決于某個事件是否發生;

缺點:

盡管松耦合的通信方式優于對象之間的硬編碼,代碼流程依然是硬傷,因為你可能不知道在哪里定義了什么回調函數觸發了什么事件發生;

發布/訂閱

這是一種類似事件監聽但是卻更強大的設計模式(很多人也把兩者視為同一種模式,個人覺得還是有點區別的),例如:

前者由目標對象負責調度,后者由一個調度中心負責調度;

前者低耦合,后者不耦合;

例如我們可以自己封裝一個方法以實現更多監聽事件

var listener = (function() {
  //訂閱隊列
  var list = [],
    //監聽事件
    on = function(type, fn) {
      if (!list[type]) list[type] = [];
      list[type].push(fn);
    },
    //取消監聽事件
    off = function(type, fn) {
      list[type] = [];
    },
    //觸發事件
    trigger = function() {
      //取出監聽類型
      var type = Array.prototype.shift.call(arguments),
        queue = list[type],
        i = 0,
        len = queue.length;

      for (; i < len; i++) {
        //帶參數發布
        queue[i].apply(this, arguments);
      }
    };

  return {
    on: on,
    off: off,
    trigger: trigger,
  };
})();

listener.on("log", function() {
  console.log("I trigger a log!");
});
listener.trigger("log");

//取消之后不觸發
listener.off("log");
listener.trigger("log");

//輸出
//I trigger a log!
優劣點 優點:

調度中心統一管理目標對象和觀察者對象相關信息,之間互不依賴;

Promise

上面的方法不管怎么變化,始終避不開回調地獄的困境,直到后來被社區提出和實現并且已經被放置到ES6統一標準化的---Promise出現.
簡單來說就兩個特性:

初始化時候的pending(進行中)狀態只能通過異步操作的結果更改成fulfilled/resolved(已成功)或者rejected(已失敗)的狀態,即其他任何手段都不能影響到;

狀態一旦更改是不可逆,即使之后再添加回調函數也只是返回同個結果,這個特點恰恰能解決上面說的回調函數無法被外層代碼捕捉錯誤的缺點;

const promise = new Promise((resolve, reject) => {
  true ? resolve("success") : reject("error");
});
//Promise實例
console.log(promise);

promise.then(
  function resolved(res) {
    console.log(res);
  },
  function rejected(err) {
    console.log(err);
  }
);

//輸出
//Promise {  }
//success

Promise構造函數接受一個函數作為參數,JavaScript 引擎提供兩個參數分別是:

參數名 用途
resolve 將Promise對象的狀態從pending(進行中)變為resolved(已成功)
reject 將Promise對象的狀態從pending(進行中)變為rejected(已失敗)

其中rejected(已失敗)的狀態包括程序的錯誤也能捕捉并且不會中斷程序運行,并把錯誤原因當成參數傳遞下去

const promise = new Promise(function(resolve, reject) {
  //未定義變量
  return num;
});

promise.then(null, function rejected(err) {
  console.log(`rejected:${err}`);
});

//輸出
//rejected: ReferenceError: num is not defined
then

Promise 實例能訪問定義在原型對象Promise.prototype上的then方法.它的作用是為 Promise 實例添加狀態改變時的回調函數,提供兩個參數分別是

參數名 用途
resolved resolved狀態的回調函數
rejected(可選) 是rejected狀態的回調函數,如果發生錯誤但沒有處理函數會程序報錯而不是狀態失敗報錯
const promise = new Promise(function(resolve, reject) {
  throw "error";
});

//有rejected處理函數
promise.then(
  function resolved(res) {
    console.log(res);
  },
  function rejected(err) {
    console.log(`捕捉錯誤:${err}`);
  }
);

//沒有rejected處理函數
promise.then(function resolved(res) {
  console.log(res);
});

//捕捉錯誤:error
//系統報錯

因為then返回的是一個新的Promise實例,所以可以鏈式調用,如果需要把當前結果作為下一個then方法的參數將它作為返回值return.

const promise = new Promise((resolve, reject) => {
  true ? resolve("success") : reject("error");
});

promise
  .then(function resolved(res) {
    console.log(res);
    //這次不返回結果了
  })
  .then(function resolved(res) {
    console.log(res);
    //這次返回其他
    return "成功";
  })
  .then(
    function resolved(res) {
      console.log(`成功信息:${res}`);
    },
    function rejected(err) {
      console.log(`失敗信息:${err}`);
    }
  );

//輸出
//success
//undefined
//成功信息:成功

Promise代碼量看著多只是為了讓讀者更直觀,采用ES6箭頭函數跟縮寫名稱可以壓縮成下面這樣

const promise = new Promise ((resolve, reject) => {
  true ? resolve ("success") : reject ("error");
});

promise.then (res => res, err => err);
return返回值

then方法會隱性返回一個Promise或者開發者顯性返回任何值,而Promise有自己內部機制處理:

Promise狀態改變前

如果回調函數中返回一個值或者不返回:

then返回resolved狀態Promise,并且將返回的值或者undefined作為參數值.

如果回調函數拋出錯誤:

then返回rejected狀態Promise,并且將拋出的錯誤作為參數值.

如果回調函數返回一個Promise:

Promise是pending狀態,then返回pending狀態Promise,但是兩者的終態還會相同的,并且將回調函數Promise的參數值作為它的參數值;

Promise是resolved狀態,then返回resolved狀態Promise,并且將回調函數Promise的參數值作為它的參數值;

Promise是rejected狀態,then返回rejected狀態Promise,并且將回調函數Promise的參數值作為它的參數值;

Promise狀態改變后

前面說過狀態一旦更改是不可逆,即使之后再添加回調函數也只是返回同個結果

resolved狀態

即使后面再拋出錯誤then也是返回resolved狀態Promise;

const promise = new Promise ((resolve, reject) => {
  resolve ();
  throw "error";
});

promise.then (
  res => {
    console.log (`resloved: ${res}`);
  },
  err => {
    console.log (`rejected: ${err}`);
  }
);

//輸出
//resloved: undefined
rejected狀態

即使后面再顯性返回resolved狀態Promise也是返回rejected狀態Promise;

const promise = new Promise ((resolve, reject) => {
  reject ();
  return Promise.resolve ();
});

promise.then (
  res => {
    console.log (`resloved: ${res}`);
  },
  err => {
    console.log (`rejected: ${err}`);
  }
);

//輸出
//rejected: undefined
例子

基本條件如下:

p1設定三秒后改變狀態

p2設定一秒后改變狀態

p1作為p2的返回參數傳遞

p1返回resolved狀態,p2返回resolved狀態.
//開始執行時間
console.log (`開始運行時間: ${new Date ().toTimeString ()}`);
const p1 = new Promise ((resolve, reject) => {
  //3秒后執行
  setTimeout (resolve, 3000);
}),
  p2 = new Promise ((resolve, reject) => {
    //1秒后執行
    setTimeout (() => resolve (p1), 1000);
  });

//處理時間
p2.then (
  res => {
    console.log (`p2返回成功狀態時間: ${new Date ().toTimeString ()}`);
  },
  err => {
    console.log (`p2返回失敗狀態時間: ${new Date ().toTimeString ()}`);
  }
);

//輸出
//開始運行時間: 09:27:44 GMT+0800 (中國標準時間)
//p2返回成功狀態時間: 09:27:47 GMT+0800 (中國標準時間)
p1返回rejectd狀態,p2返回resolved狀態.
//開始執行時間
console.log(`開始運行時間: ${new Date().toTimeString()}`);
const p1 = new Promise((resolve, reject) => {
    //3秒后執行
    setTimeout(reject, 3000);
  }),
  p2 = new Promise((resolve, reject) => {
    //1秒后執行
    setTimeout(() => resolve(p1), 1000);
  });

//處理時間
p2.then(
  res => {
    console.log(`p2返回成功狀態時間: ${new Date().toTimeString()}`);
  },
  err => {
    console.log(`p2返回失敗狀態時間: ${new Date().toTimeString()}`);
  }
);

//輸出
//開始運行時間: 09:30:50 GMT+0800 (中國標準時間)
//p2返回失敗狀態時間: 09:30:53 GMT+0800 (中國標準時間)
p1返回resolved狀態,p2返回rejectd狀態.
//開始執行時間
console.log (`開始運行時間: ${new Date ().toTimeString ()}`);
const p1 = new Promise ((resolve, reject) => {
  //3秒后執行
  setTimeout (resolve, 3000);
}),
  p2 = new Promise ((resolve, reject) => {
    //1秒后執行
    setTimeout (() => reject (p1), 1000);
  });

//處理時間
p2.then (
  res => {
    console.log (`p2返回成功狀態時間: ${new Date ().toTimeString ()}`);
  },
  err => {
    console.log (`p2返回失敗狀態時間: ${new Date ().toTimeString ()}`);
  }
);

//輸出
//開始運行時間: 09:31:33 GMT+0800 (中國標準時間)
//p2返回失敗狀態時間: 09:31:34 GMT+0800 (中國標準時間)

看完三個例子之后還有一個省略代碼可以得出結論:

p1 p2 then 間隔(s)
resolved resolved resolved 3
rejectd resolved rejectd 3
resolved rejectd rejectd 1
rejectd rejectd rejectd 1

結論:then回調函數的Promise狀態首先取決于調用函數(p2)狀態,當它resolved(已成功)情況下才取決于返回函數Promise(p1)的狀態.

catch

如果Promise狀態變為rejected或者then方法運行中拋出錯誤,都可以用catch捕捉并且返回一個新的Promise實例,建議then方法省略錯誤處理,最底層添加一個catch處理機制;

const promise = new Promise((resolve, reject) => {
  throw "error";
});

//省略then錯誤處理
promise.then().catch(res => {
  console.log(res);
});

//輸出
//error

注意:如果決定使用catch處理的話前面就不能用reject做錯誤處理了,因為被攔截之后是不會再經過catch了

const promise = new Promise((resolve, reject) => {
  throw "error";
});

promise
  .then(null, err => {
    console.log(`then: ${err}`);
    return err;
  })
  .catch(err => {
    //跳過
    console.log(`catch: ${err}`);
  })
  .then(
    res => {
      console.log(`resloved: ${res}`);
    },
    err => {
      console.log(`rejected: ${err}`);
    }
  );

//輸出
//then: error
//resloved: error

從結果可以看到第一個then有了錯誤處理函數之后會跳過catch方法,然后第二個then會在成功處理函數里打印?說好的狀態一旦更改是不可逆呢???
這里面又涉及到return值的問題了.

第一層then已經做了錯誤處理,返回一個resloved狀態Promise;
因為catch不會處理resloved狀態Promise所以跳過;
第二層then接收并在resloved處理函數處理;

如果記性好的人應該記得上面講解例子有個類似的寫法但是卻依然報錯,知道原因么?

const promise = new Promise(function(resolve, reject) {
  throw "error";
});

//有rejected處理函數
promise.then(
  function resolved(res) {
    console.log(res);
  },
  function rejected(err) {
    console.log(`捕捉錯誤:${err}`);
  }
);

//沒有rejected處理函數
promise.then(function resolved(res) {
  console.log(res);
});

//捕捉錯誤:error
//系統報錯

再重復一遍,Promise每次都會返回一個新的Promise實例,所以不能用鏈式調用后的結果來看待變量promise.

靜態方法 Promise還提供多個方法控制流程:
方法 作用
resolve 快速返回一個新的 Promise 對象,狀態為resolved的實例
reject 快速返回一個新的 Promise 對象,狀態為rejected的實例
finally 不管 Promise 對象最后狀態如何,都會執行的操作,回調函數不接受任何參數這表明finally方法里面的操作,應該是與狀態無關的,不依賴于 Promise 的執行結果。該方法是 ES2018 引入標準的,就不說了
all
The Promise.all(iterable) method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.

大概意思就是當迭代器參數里的所有Promise都返回成功狀態或者沒有Promise入參的時候返回一個成功狀態Promise,否則返回第一個失敗狀態的Promise拋出的錯誤.
如果迭代器參數不是Promise也會被隱性調用Promise.resolve方法轉成Promis實例.(Promise.all方法的參數必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例).

//成功
const p1 = new Promise((resolve, reject) => {
    resolve("suc1");
  }),
  //成功
  p2 = new Promise((resolve, reject) => {
    resolve("suc2");
  }),
  //失敗
  p3 = new Promise((resolve, reject) => {
    reject("err");
  });

Promise.all([p1, p2]).then(res => {
  console.log(`全部成功:${res}`);
});

Promise.all([p1, p3]).then(null, err => console.log(`第一個失敗:${err}`));

Promise.all([]).then(
  res => console.log("空數組返回成功Promise"),
  err => console.log("空數組返回失敗Promise")
);

//輸出
//空數組返回成功Promise
//全部成功:  (2) ["suc1", "suc2"]
//第一個失敗: err

注意: 如果空數組情況下立馬返回成功狀態,盡管放在最后位置卻第一個打印結果!

race

跟all類似,區別在于兩點:

它只返回第一個改變Promise狀態的結果,不管成功失敗都只返回一個結果;

空數組情況下永遠不會執行;

//成功
const p1 = new Promise((resolve, reject) => {
    resolve("suc1");
  }),
  //成功
  p2 = new Promise((resolve, reject) => {
    resolve("suc2");
  }),
  //失敗
  p3 = new Promise((resolve, reject) => {
    reject("err");
  });

Promise.race([p1, p2]).then(res => {
  console.log(`第一個返回成功:${res}`);
});

Promise.race([p3, p2]).then(null, err => console.log(`第一個返回失敗:${err}`));

Promise.race([]).then(
  res => console.log("空數組返回成功Promise"),
  err => console.log("空數組返回失敗Promise")
);

//輸出
//第一個返回成功:suc1
//第一個返回失敗:err
使用要點

因為Promise的then方法會把函數結果放置到微任務隊列(micro tasks),也就是當次事件循環的最后執行.如果你使用的是同步函數實際上是被無端延遲執行了.如果不清楚這方面內容可以再看一下我之前寫得Javascript執行機制--單線程,同異步任務,事件循環

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

function async() {
  setTimeout(() => console.log("async"), 1000);
}

Promise.resolve().then(sync);
Promise.resolve().then(async);
console.log("end");

//輸出
//end
//sync
//async

實際上有兩種方法可以實現讓同步函數同步執行,異步函數異步執行,并且讓它們具有統一的 API.

匿名函數立即執行new Promise()
function sync() {
  console.log("sync");
}

function async() {
  setTimeout(() => console.log("async"), 1000);
}

(() => new Promise(resolve => resolve(sync())))();

(() => new Promise(resolve => resolve(async())))();

console.log("end");

// 輸出
// sync
// end
// async
async,如果不知道沒關系,下面有講到
function sync() {
  console.log("sync");
}

function async() {
  setTimeout(() => console.log("async"), 1000);
}
(async () => sync())();
(async () => async())();
console.log("end");

// 輸出
// sync
// end
// async
優劣點 優點:

異步流程能使用線性寫法鏈式調用,擺脫“回調地獄”;

多種方式的錯誤處理機制;

能將當前結果往下層層傳遞;

promise是一種規約,它在回調調用和錯誤處理規范化的基礎上給異步編程提供了更多的可能性;

缺點:

一旦開始無法停止取消;

盡管可以用catch做統一錯誤處理,但是被中間層攔截了之后可能會造成干擾;

只能知道Promise在實例pending(進行中)、fulfilled(已成功)和rejected(已失敗)狀態中的一種,沒有更加詳細的進度信息;

盡管比回調函數稍好,但是寫法也十分冗余,過多的then讓代碼語義化不明;

每次都是返回新的Promise實例,犧牲性能代價略高;

Generator

Generator 函數是 ES6 提供的一種異步編程解決方案.

原理

語法上,首先可以把它理解成,Generator 函數是一個狀態機,封裝了多個內部狀態.執行 Generator 函數并不執行里面程序而是返回一個遍歷器對象,可以依次遍歷 Generator 函數內部的每一個狀態.yield表達式是暫停執行的標記,而next方法可以恢復執行.
每一次調用next方法,都會返回數據結構的當前成員的信息.具體來說,就是返回一個包含value和done兩個屬性的對象.其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束.

特征

function關鍵字與函數名之間有一個星號就行,哪個位置都能通過;

函數體內部使用yield表達式定義不同的內部狀態;

function* Generator() {
  yield "1";
  return "2";
}

const example = Generator();
console.log(example);
console.log(example.next());
console.log(example.next());

// 輸出
// Object [Generator] {}
// { value: "1", done: false }
// { value: "2", done: true }
next表達式運行邏輯

yield表達式本身沒有返回值,或者說總是返回undefined.next方法可以帶一個參數,該參數就會被當作上一個yield表達式的返回值.否則上一個yield表達式的返回值維持原狀不變,這個特性相當重要,我們就依賴它從外部向內部注入不同的值,從而調整函數行為.調用next的時候,內部指針會往下運行直到遇到下列情況:

遇到yield表達式,它后面的表達式只有內部指針指向該語句時才會執行,并將它的值作為返回的vaule,然后暫停執行;

遇到return表達式,將它的值作為返回的vaule,遍歷結束;

運行到函數結束,將undefined作為返回的vaule,遍歷結束;

2和3之后仍然可以繼續調用next,結果都是{value: undefined, done: true},即使return后面還有yield表達式也不會改變結果;

然后我們根據規則再去解讀那一段代碼的運行結果

function* foo() {
  let x = yield 1;
  console.log(`函數內部x: ${x}`);

  let y = x * (yield x + 2);
  console.log(`函數內部xy: ${x} ${y}`);

  return x + y;
}

const it = foo();
console.log(it.next(2));
console.log(it.next(3));
console.log(it.next(4));

// 輸出
// 函數內部x: undefined
// Object { value: 1, done: false }
// 函數內部x: 3
// Object { value: 5, done: false }
// 函數內部xy: 3 12
// Object { value: 15, done: true }

第一次next: 執行var x = yield 1后停止,因為沒有上一個yield表達式,所以入參2不起效,輸出1;

第二次next: 執行yield x + 2后停止,x等于入參3,輸出5;

第三次next: 執行return x + y后停止,上一個yield表達式var y = 3 * 4(x+2被入參4取代了),輸出15;

概念其實很清晰,過程有點復雜,大家謹記next方法可以帶一個參數,該參數就會被當作上一個yield表達式的返回值.否則上一個yield表達式的返回值維持原狀不變,然后根據這個規則來算就明白了.

yield* 表達式運行邏輯

Generator函數內部,調用另一個Generator函數,默認情況下是沒有效果的,需要用到yield表達式,帶*會返回值,不帶*返回遍歷器對象.

function* foo() {
  bar(1);
  //內部調用Generator函數
  yield bar(2);
  yield* bar(3);
  yield 1;
}

function* bar(num) {
  console.log(num);
  yield 2;
}

const it = foo();
console.log(it.next());
console.log(it.next());
console.log(it.next());

// 輸出
// { value: Object [Generator] {}, done: false }
// 3
// { value: 2, done: false }
// { value: 1, done: false }

從輸出結果可知只有yield * bar(3)生效.
如果被代理的 Generator 函數有return語句,那么就可以向代理它的Generator函數返回數據.

function* genFuncWithReturn() {
  yield "a";
  yield "b";
  return "c";
}

function* logReturned(genObj) {
  const result = yield* genObj;
  //返回值
  console.log(result);
}

const it = logReturned(genFuncWithReturn());
console.log(it.next());
console.log(it.next());
console.log(it.next());

// 輸出
// { value: "a", done: false }
// { value: "b", done: false }
// c
// { value: undefined, done: true }
易錯點 Generator函數不用yield表達式就只剩一個暫緩執行的作用;
function* foo() {
  console.log(1);
  console.log(2);
  return;
}

const it = foo();
console.log(3);
it.next();

// 輸出
// 3
// 1
// 2
非Generator函數中使用yield表達式會句法錯誤;
function foo() {
    yield 1;
}

const it = foo();

// 輸出
// Unexpected number
yield表達式如果用在另一個表達式之中,必須放在圓括號里面,除非用作函數參數或放在賦值表達式的右邊
function* foo() {
  console.log("函數內部x: " + (yield));
  yield 1;
}

const it = foo();
console.log(it.next());
console.log(it.next(3));

// 輸出
// { value: undefined, done: false }
// 函數內部x: 3
// { value: 1, done: false }
原型方法 throw

Generator函數返回的遍歷器對象的throw方法(不是全局的throw方法)可以在函數體外拋出錯誤,這意味著出錯的代碼與處理錯誤的代碼,實現了時間和空間上的分離.然后在Generator函數體內被捕獲后,會附帶執行下一條yield表達式.

const err = function*() {
  try {
    yield;
  } catch (e) {
    console.log(`內部捕獲: ${e}`);
  }
};
//執行
const it = err();
console.log(it.next());

//外部捕獲錯誤
try {
  //兩次拋出錯誤
  console.log(it.throw("err1"));
  console.log(it.throw("err2"));
} catch (e) {
  console.log(`外部捕獲: ${e}`);
}

// 輸出
// { value: undefined, done: false }
// 內部捕獲 err1
// { value: undefined, done: true }
// 外部捕獲 err2

所以這里實際執行順序it.throw("err1") ? it.next() ? it.throw("err2")
同時,Generator函數體內拋出的錯誤,也可以被函數體外的catch捕獲;

const err = function*() {
  yield "1";
  throw "err";
  yield;
  yield "2";
};
//執行
const it = err();
console.log(it.next());

try {
  console.log(it.next());
} catch (e) {
  console.log("外部捕獲", e);
}
//捕捉錯誤之后繼續執行
console.log(it.next());

// 輸出
// { value: "1", done: false }
// 外部捕獲 err
// { value: undefined, done: true }

注意最后一次執行next函數返回{value: undefined, done: true},因為拋出錯誤后沒有做內部捕獲,JS引擎會認為這個Generator函數已經遍歷結束.

return

返回給定的值或者不傳默認undefined,并且終結遍歷 Generator 函數.

function* Generator() {
  yield "1";
  yield "2";
  yield "3";
  return "4";
}

const it = Generator();
console.log(it.return(2));
console.log(it.next(3));

// 輸出
// { value: 2, done: true }
// { value: undefined, done: true }
實踐寫法
//模擬ajax請求
function ajax() {
  return new Promise((resolve, reject) => {
    setTimeout(
      () =>
        resolve({
          abc: 123,
        }),
      1000
    );
  });
}

function* foo() {
  try {
    yield;
  } catch (err) {
    console.log(`內部捕獲: ${err}`);
  }
  yield console.log("再次執行");
  yield ajax();
  return res;
}

const it = foo();
it.next();
it.throw("somethings happend");
it.next().value.then(res => {
  console.log(res);
});

// 輸出
// 內部捕獲: somethings happend
// 再次執行
// { abc: 123 }

暫停執行和恢復執行,這是Generator能封裝異步任務的根本原因,強大的錯誤捕捉能力可以在寫法上更加自由.
我們可以在上面的代碼擴展出更多步驟,例如這種異步依賴的代碼可以以同步順序寫出來,或者外層操作res1之后再傳到ajax2等.

function* foo() {
  var res1 = yield ajax1(),
    res2 = yield ajax2(res1);

  return res;
}

盡管Generator異步流程管理非常簡潔,但是操作流程不算方便,需要開發者決定什么時候執行下一步,甚至你會發現在整個異步流程會充斥著多個next的身影.

自動遍歷方法

for...of循環可以自動遍歷 Generator 函數時生成的Iterator對象,一旦next方法的返回對象的done屬性為true,循環就會中止,且不包含該返回對象,所以return語句會執行,但不會顯示輸出.

function* Generator() {
  yield "1";
  yield "2";
  yield "3";
  return "4";
}

for (let v of Generator()) {
  console.log(v);
}

// 輸出
// 1
// 2
// 3

也可以用while實現,區別在于for...of循環返回值,while返回數據結構

function* Generator() {
  yield "1";
  yield "2";
  yield "3";
  return "4";
}

let it = Generator(),
  result = it.next();

while (!result.done) {
  console.log(result);
  result = it.next(result);
}

// 輸出
// { value: "1", done: false }
// { value: "2", done: false }
// { value: "3", done: false }
異步衍生庫co

上面方法不適用于異步操作,如果之間需要依賴關系的話可能會導致后續執行失敗,為了實現這種效果有些很出名的衍生庫co等,關鍵源碼很短也比較簡單,直接貼出來了.

/**
 * Execute the generator function or a generator
 * and return a promise.
 *
 * @param {Function} fn
 * @return {Promise}
 * @api public
 */

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  return new Promise(function(resolve, reject) {
    if (typeof gen === "function") gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== "function") return resolve(gen);

    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */

    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(
        new TypeError(
          "You may only yield a function, promise, generator, array, or object, " +
            "but the following object was passed: "" +
            String(ret.value) +
            """
        )
      );
    }
  });
}

完整代碼請看co,具體用法就不說了

優劣點 優點

一種新的異步編程概念,ES6對協程的不完全實現,使用同步的方式處理異步操作,改寫回調函數;

出色的控制流管理,Generator內部可以使用yield表達式定義不同的內部狀態并且惰性執行;

yield表達式具備記憶功能,next可以從上一次暫停點開始執行;

通過next入參和返回值可以交換數據影響執行函數;

Generator函數體外拋出的錯誤,可以在函數體內捕獲;反過來,Generator函數體內拋出的錯誤,也可以被函數體外的catch捕獲;

缺點

相對復雜,需要了解更多的知識點,怎么正確使用Generator有很多注意地方,具體詳情自行查看文檔;

流程管理不夠靈活,衍生的co庫等都有些不足;

犧牲性能代價比較高;

Async Await

ES2017 標準引入了 async 函數,一句話,它就是 Generator 函數的語法糖.
如果說 Promise 主要解決的是異步回調問題,那么 async + await 主要解決的就是將異步問題同步化,降低異步編程的認知負擔.

特征 將Generator的*換成async,yeild換成await; 內置處理器,自動執行 一旦遇到await就會先返回,等到異步操作完成,再接著執行函數體內后面的語句
function p1() {
  return Promise.resolve().then(res => console.log("p1"));
}

function p2() {
  return Promise.resolve().then(res => console.log("p2"));
}

async function Async() {
  console.log(1);
  await p1();
  console.log(2);
  await p2();
}

const it = Async().then(res => console.log(123));

// 輸出
// 1
// p1
// 2
// p2
// 123
如果await命令后面是非Promise對象會被強制轉換成promise對象
async function Async() {
  return await 1;
}
Async().then(res => console.log(res));

// 輸出
// 1
如果await命令后面的Promise對象為reject狀態,就會中斷整個Async函數
async function Async() {
  await Promise.reject("err");
  console.log("不執行了");
  await Promise.resolve();
};
Async().catch(err => console.log(err));

// 輸出
// err
使用要點 聯合Promise.all在遍歷使用
//阻塞主線程
const delazy = (time = 2000) =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, time);
  });

//模擬請求
async function quest() {
  await delazy();
  return Promise.resolve({
    str: "123",
  });
}

//統一獲取
async function doSomethings() {
  const ary = await Promise.all([quest(), quest()]);
  console.log(ary.map(e => e.str));
}

doSomethings();
為了保證不因為某個環節失敗中斷整個方法,建議都放在try..catch中
async function Async() {
  try {
    await Promise.reject("err").then(null, err => console.log(err));
    console.log("繼續執行");
    await Promise.resolve("suc").then(res => console.log(res));
  } catch (err) {}
}
Async();

// 輸出
// 繼續執行
// suc
沒有依賴關系的方法不要使用await
function p1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
  });
}

function p2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(2), 1000);
  });
}

async function sum() {
  const [a, b] = await Promise.all([p1(), p2()]);
  console.log(a, b);
}
==================== OR ============================
async function sum() {
  const a = p1(),
    b = p2(),
    c = await a,
    d = await b;

  console.log(c, d);
}

sum();

// 輸出
// 1 2
總結:

如果想要跳過錯誤繼續執行的解決辦法可以使用try..catch或者catch;

沒有依賴關系的異步操作可以直接用await后面接Promise.all()提高性能;

實現原理(全部代碼源自阮一峰大神的博客)

跟上面說的co類似,將Generator函數和自動執行器,包裝在一個函數里.

async function fn(args) {}
//等價于
function fn(args) {
  return spawn(function*() {});
}

spawn函數就是自動執行器

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch (e) {
        return reject(e);
      }
      if (next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(
        function(v) {
          step(function() {
            return gen.next(v);
          });
        },
        function(e) {
          step(function() {
            return gen.throw(e);
          });
        }
      );
    }
    step(function() {
      return gen.next(undefined);
    });
  });
}

代碼量不多也不難,建議認真學習里面的思路

優劣點 優點

內置執行器,不暴露給用戶;

更好的語義化,更簡潔的寫法;

async函數的await命令后面,可以是Promise對象和原始類型的值(數值、字符串和布爾值,但這時等同于同步操作);

async函數的返回值是Promise;

缺點

只要一個await語句后面的Promise變為reject,那么整個async函數都會中斷執行;

錯誤處理機制復雜;

犧牲性能代價更高;

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/106388.html

相關文章

  • ES6-7

    摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評論0 收藏0
  • 前端_JavaScript

    摘要:為此決定自研一個富文本編輯器。例如當要轉化的對象有環存在時子節點屬性賦值了父節點的引用,為了關于函數式編程的思考作者李英杰,美團金融前端團隊成員。只有正確使用作用域,才能使用優秀的設計模式,幫助你規避副作用。 JavaScript 專題之惰性函數 JavaScript 專題系列第十五篇,講解惰性函數 需求 我們現在需要寫一個 foo 函數,這個函數返回首次調用時的 Date 對象,注意...

    Benedict Evans 評論0 收藏0
  • 什么是Node.js

    Node.js從2009年誕生至今,已經發展了兩年有余,其成長的速度有目共睹。從在github的訪問量超過Rails,到去年底Node.jsS創始人Ryan Dalh加盟Joyent獲得企業資助,再到今年發布Windows移植版本,Node.js的前景獲得了技術社區的肯定。InfoQ一直在關注Node.js的發展,在今年的兩次Qcon大會(北京站和杭州站)都有專門的講座。為了更好地促進Node.j...

    CrazyCodes 評論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...

    tuniutech 評論0 收藏0
  • [譯]學習如何去學習 JavaScript - 5 個你應該如何花在學習 JS 上時間建議

    摘要:擁抱異步編程縱觀發展史也可以說成開發的發展史,你會發現異步徹底改變了這場游戲。可以這么說,異步編程已成為開發的根基。這也是你應盡早在上投入大量時間的一處核心知識點,這其中包含和等重要概念。這也是最突出的一項貢獻。 原文地址:Medium - Learning How to Learn JavaScript. 5 recommendations on how you should spend ...

    wanglu1209 評論0 收藏0

發表評論

0條評論

princekin

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<