擁抱Generator,告別異步回調

本節學習目標ajax

  • 實現一個生成器 :什麼是迭代器?什麼是生成器?generator概念,generator函數語法;編程

  • 異步處理 :瞭解異步編程;哪些解決辦法;結合generator來解決異步;json


Generator概念

Generator很像是一個函數,可是你能夠暫停它的執行。你能夠向它請求一個值,因而它爲你提供了一個值,可是餘下的函數不會自動向下執行直到你再次向它請求一個值。api

什麼是生成器?
function* quips(name) {
  yield "你好 " + name + "!";
  yield "你喜歡我嗎";
  if (name.startsWith("yang")) {
    yield "咱們五百年前是一家誒";
  }
  yield "再見!";
}
> var iter = quips("yang jiang");
  [object Generator]
> iter.next()
  { value: "你好 yang jiang!", done: false }
> iter.next()
  { value: "咱們五百年前是一家誒", done: false }
> iter.next()
  { value: "再見!", done: false }
> iter.next()
  { value: undefined, done: true }

生成器調用看起來很是相似:quips("yang jiang")。可是,當你調用一個生成器時,它並不是當即執行,而是返回一個已暫停的生成器對象(上述實例代碼中的iter)。你可將這個生成器對象視爲一次函數調用,只不過當即凍結了,它剛好在生成器函數的最頂端的第一行代碼以前凍結了。promise

每當你調用生成器對象的.next()方法時,函數調用將其自身解凍並一直運行到下一個yield表達式,再次暫停。瀏覽器

若是用專業術語描述,每當生成器執行yields語句,生成器的堆棧結構(本地變量、參數、臨時值、生成器內部當前的執行位置)被移出堆棧。然而,生成器對象保留了對這個堆棧結構的引用(備份),因此稍後調用.next()能夠從新激活堆棧結構而且繼續執行。app

什麼是迭代器?
class RangeIterator {
  constructor(start, stop) {
    this.value = start;
    this.stop = stop;
  }

  [Symbol.iterator]() { return this; }

  next() {
    var value = this.value;
    if (value < this.stop) {
      this.value++;
      return {done: false, value: value};
    } else {
      return {done: true, value: undefined};
    }
  }
}

// 返回一個新的迭代器,能夠從start到stop計數。
function range(start, stop) {
  return new RangeIterator(start, stop);
}

查看運行結果異步

生成器就是迭代器!全部的生成器都有內建.next()和[Symbol.iterator]()方法的實現。你只須編寫循環部分的行爲。async

function* range(start, stop) {
  for (var i = start; i < stop; i++)
    yield i;
}

咱們能夠經過一個類比demo來進一步熟悉生成器,你可能對此感到陌生,可是並不難理解。異步編程

// 取貨碼1.0
function* ticketGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

var takeANumber = ticketGenerator()
console.log(takeANumber.next()) //{value: 1, done: false}
console.log(takeANumber.next()) //{value: 2, done: false}
console.log(takeANumber.next()) //{value: 3, done: false}
console.log(takeANumber.next()) //{value: undefined, done: true}
//取貨碼2.0
//無限循環取貨碼
function* ticketGenerator() {
  for(var i=0; true; i++) 
    yield i; //必定要打分號
}
var takeANumber = ticketGenerator()
console.log(takeANumber.next().value) //{value: 1, done: false}
console.log(takeANumber.next().value) //{value: 2, done: false}
console.log(takeANumber.next().value) //{value: 3, done: false}
console.log(takeANumber.next().value) //{value: 4, done: false}
//每一次當咱們調用next()時,
//generator執行下一個循環迭代而後暫停。
//這意味着咱們擁有一個能夠無限向下運行的generator。
//由於這個generator只是發生了暫停,你並無凍結你的程序。
//事實上,generator是一個建立無限循環的好方法。
//取貨碼2.2
//經過改變給next傳遞一個值,它會被視爲generator中的一個yield語句的結果來對待。
function* ticketGenerator(){
    for(var i=0; true; i++){
        var reset = yield i;
        if(reset) {i = -1;}
    }
}
var takeANumber = ticketGenerator(); 
console.log(takeANumber.next().value); //0  
console.log(takeANumber.next().value); //1 
console.log(takeANumber.next().value); //2 
console.log(takeANumber.next(true).value); //0 
console.log(takeANumber.next().value); //1

它與普通函數有不少共同點,可是兩者有以下區別:

  • 普通函數使用function聲明,而生成器函數使用function*聲明。

  • 在生成器函數內部,有一種相似return的語法:關鍵字yield。兩者的區別是,普通函數只能夠return一次,而生成器函數能夠yield屢次(固然也能夠只yield一次)。在生成器的執行過程當中,遇到yield表達式當即暫停,後續可恢復執行狀態。

這就是普通函數和生成器函數之間最大的區別,普通函數不能自暫停,生成器函數能夠。

demo(斐波那契數列)

生成器函數和 yield 結合來生成斐波那契數列(前兩個數字都是 1 ,除此以外任何數字都是前兩個數之和的數列)

function fab(max) {
    var count = 0, last = 0, current = 1;

    while(count++ < max) {
        yield current;
        var tmp = current;
        current += last;
        last = tmp;
    }
}

for(var i of fib(10)) {
    console.log(i);
}

異步

Javascript語言的執行環境是"單線程"(single thread)。所謂"單線程",就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。

這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。
爲了解決這個問題,Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。

  • 回調函數

  • 事件監聽

  • 發佈/訂閱

  • Promises對象

f1().then(f2).fail(f3);

Promise

Promise有不少版本,也有不少實現的庫,可是這裏主要是介紹ES6標準的內容。若是閱讀如下幾條特性以爲不懂的話建議先看看上面兩本書相應的章節。

關於promise,首先要意識到它是一種對象。這種對象能夠用Promise構造函數來建立,也能夠經過Nodejs自己一些默認的返回來獲取這種對象。
promise對象有三種狀態:Pending,Fulfilled,Rejected。分別對應着未開始的狀態,成功的狀態,以及失敗的狀態。
這種對象經常封裝着異步的方法。在異步方法裏面,經過resolve和reject來劃定何時算是成功,何時算是錯誤,同時傳參數給這兩個函數。這些參數就是異步獲得的結果或者錯誤。
異步有成功的時候,也有錯誤的時候。對象經過then和catch方法來規定異步結束以後的操做(正確處理函數/錯誤處理函數)。而then和catch是Promise.prototype上的函數,所以「實例化」以後(其實並不是真正的實例)能夠直接使用。
這個promise對象還有一個神奇的地方,就是能夠級聯。每個then裏面返回一個promise對象,就又像上面所提的那樣,有異步就等待異步,而後選擇出規定好的正確處理函數仍是錯誤處理函數。

如何關停生成器

  • generator.return()

  • generator.next()的可選參數

  • generator.throw(error)

  • yield*

生成器能夠用來實現異步編程,完成你用異步回調或promise鏈所作的一切。

生成器的.next()方法接受一個可選參數,參數稍後會做爲yield表達式的返回值出如今生成器中。那就是說,yield語句與return語句不一樣,它是一個只有當生成器恢復時纔會有值的表達式。

結合生成器實現更多功能

普通yield表達式只生成一個值,而yield*表達式能夠經過迭代器進行迭代生成全部的值。

function* concat(iter1, iter2) {
 for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}
function* concat(iter1, iter2) {
      yield* iter1;
      yield* iter2;
}
fetch(url, {
  method: "POST",
  body: JSON.stringify(data),
  headers: {
    "Content-Type": "application/json"
  },
  credentials: "same-origin"
}).then(function(response) {
  response.status     //=> number 100–599
  response.statusText //=> String
  response.headers    //=> Headers
  response.url        //=> String

  return response.text()
}, function(error) {
  error.message //=> String
})

回調地獄

/**
* 第一個是抓取文章列表的api
* 第二個是給文章id, 抓取文章內容的api
* 第三個是給做者id, 返回做者資訊的api
*/

getArticleList(function(articles){
    getArticle(articles[0].id, function(article){
        getAuthor(article.authorId, function(author){
            alert(author.email);
        })
    })
})

function getAuthor(id, callback){
    $.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
        author: id
    }).done(function(result){
        callback(result);
    })
}

function getArticle(id, callback){
    $.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
        id: id
    }).done(function(result){
        callback(result);
    })
}

function getArticleList(callback){
    $.ajax(
    "http://beta.json-generator.com/api/json/get/Ey8JqwIh")
    .done(function(result){
        callback(result);
    });
}
getArticleList()
     .then(articles => getArticle(articles[0].id))
     .then(article => getAuthor(article.authorId))
     .then(author => {
       alert(author.email);
     });
    
    function getAuthor(id){
        return new Promise(function(resolve, reject){
            $.ajax("http://beta.json-generator.com/api/json/get/E105pDLh",{
                author: id
            }).done(function(result){
                resolve(result);
            })
        });
    }

    function getArticle(id){
        return new Promise(function(resolve, reject){
            $.ajax("http://beta.json-generator.com/api/json/get/EkI02vUn",{
                id: id
            }).done(function(result){
                resolve(result);
            })
        });
    }

    function getArticleList(){
        return new Promise(function(resolve, reject){
           $.ajax(
            "http://beta.json-generator.com/api/json/get/Ey8JqwIh")
            .done(function(result){
                resolve(result);
            }); 
        });
    }
// 利用Generator的特性,來寫出很像同步但實際上是非同步的代碼
function* run(){
  var articles = yield getArticleList();
  var article = yield getArticle(articles[0].id);
  var author = yield getAuthor(article.authorId);
  console.log(author.email);  
}

var gen = run();
gen.next().value
    .then(articles => {
      gen.next(articles).value.then(article => {
        gen.next(article).value.then(author => {
          gen.next(author)
      })
    })
  })
async function run(){
  var articles = await getArticleList();
  var article = await getArticle(articles[0].id);
  var author = await getAuthor(article.authorId);
  alert(author.email);  
}
相關文章
相關標籤/搜索