ES6中的異步編程:Generators函數(一)

訪問原文地址javascript

對ES6的generators的介紹分爲3個部分java

  • 第一部分base介紹及使用es6

  • 第二部分基於generators和Promise實現最強大的異步處理邏輯ajax

概述

Generator函數是協程在ES6的實現,用來作異步流程的封裝,最大特色就是能夠交出函數的執行權(即暫停執行)。十分的奇葩,光看語法,簡直認不出這也是JavaScript了。因爲可使用yield語句來暫停異步操做,這讓generators異步編程的代碼,很像同步數據流方法同樣。由於從語法角度來看,generators函數是一個狀態機,封裝了多個內部狀態,經過iterator來分步調用。編程

基本語法

2個關鍵字搞定generators語法

  • function與函數名直接的星號:*json

  • 函數體內yield語句promise

function* testGenerator() {
    yield 'first yield';
    yield 'second yield';
    return 'last';
}

var gen = testGenerator();

console.log(gen.next().value);// first yield 
// { value: 'first yield', done: false }
console.log(gen.next().value);// second yield
// { value: 'second yield', done: false }
console.log(gen.next().value);// last 
// { value: 'last', done: true }

console.log(gen.next().value);// undefined

for...of遍歷

for...of循環能夠自動遍歷generators函數的iterator對象,且再也不須要調用next方法。for...of須要檢查iterator對象的done屬性,若是爲true,則結束循環,所以return語句不能被遍歷到app

for (let i of testGenerator) {
    console.log(i);
}
// first yield
// second yield

next方法的參數

yield句自己沒有返回值,或者說老是返回undefined。next方法能夠帶一個參數,該參數就會被看成上一個yield語句的返回值。異步

function *gen(){
  let arr = [];
  while(true){
    arr.push(yield arr);
  }
}

var name = gen();

console.log(name.next('first').value);//[]
console.log(name.next('second').value);//["second"]
console.log(name.next('thrid').value);//["second","thrid"]

須要注意的是,第一次執行next設置參數沒有效果。async

generators實踐

實現Fibonacci數列

遞歸實現:

function* fib (n, current = 0, next = 1) {
  if (n === 0) {
    return 0;
  }

  yield current;
  yield* fib(n - 1, next, current + next);
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}

注:若是存儲計算結果再過運算,這樣的實現比遞歸方法效率高3倍

function* fibonacci() {
  let [prev, curr] = [0, 1];
  for (;;) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  if (n > 1000) break;
  console.log(n);
}

利用for...of循環,遍歷任意對象(object)的方法

原生的JavaScript對象沒有遍歷接口,沒法使用for...of循環,經過Generator函數爲它加上這個接口,就能夠用了。

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe

ES6中iterator遍歷接口彙總

  • for...of循環

  • 擴展運算符(...)

  • 解構賦值

  • Array.from方法內部調用的

它們均可以將Generator函數返回的Iterator對象,做爲參數來使用。

function* numbers () {
  yield 1
  yield 2
  return 3
}

// 擴展運算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解構賦值
let [x, y] = numbers();
x // 1
y // 2

// for...of 循環
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2

generators與同步

generators一個特色就是代碼看上去很是像同步編程的效果

function* test() {
    yield( "1st" );
    yield( "2nd" );
    yield( "3rd" );
    yield( "4th" );
}
var iterator = test();

console.log( "== Start of Line ==" );
console.log( iterator.next().value );
console.log( iterator.next().value );
for ( var line of iterator ) {
    console.log( line );
}
console.log( "== End of Line ==" );

看下輸出,濃濃的同步執行風格。

== Start of Line ==
1st
2nd
3rd
4th
== End of Line ==

callback、Promises、Generators比較

舉例說一個場景,查詢一篇新聞文章的做者信息,流程是:請求最新文章列表->請求某文章相關id->做者id信息

callback實現

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

function getAuthor(id, callback){
    $.ajax(url,{
        author: id
    }).done(function(result){
        callback(result);
    })
}

function getArticle(id, callback){
    $.ajax(url,{
        id: id
    }).done(function(result){
        callback(result);
    })
}

function getArticleList(callback){
    $.ajax(url)
    .done(function(result){
        callback(result);
    });
}

用Promise來作

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({
            url: id+'author.json',
            success: function(data) {
              resolve(data);
          }
        })
    });
}

function getArticle(id){
    return new Promise(function(resolve, reject){
        $.ajax({
            url: id+'.json',
            success: function(data) {
              resolve(data);
          }
        })
    });
}

function getArticleList(){
    return new Promise(function(resolve, reject){
       $.ajax({
           url: 'all.json',
           success: function(data) {
             resolve(data);
         }
       }) 
    });
}

Gererator來實現

function* run(){
  var articles = yield getArticleList();
  var article = yield getArticle(articles[0].id);
  var author = yield getAuthor(article.authorId);
  alert(author.email);  
}

var gen = run();
gen.next().value.then(function(r1){
  gen.next(r1).value.then(function(r2){
      gen.next(r2).value.then(function(r3){
        gen.next(r3);
        console.log("done");
      })
  })
});

runGenerator的實現

每次都要手動去調用next方法,仍是會讓代碼變得冗長,咱們能夠設計一個專門用來運行generators的方法,並能夠抽象出來,之後就能夠作一個統一的error管理,或者獲取本地數據邏輯的變化。

Thunk函數方法

編譯器的‘傳名調用’實現,將全部的參數放到一個臨時函數中,再將這個臨時函數做爲參數傳入到函數體中。該臨時函數就叫作Thunk函數。

任何函數,只要參數有回調函數,就能寫成Thunk函數的方法。下面就是簡單的Thunk函數轉換器。

//es5
var Thunk = function(fn) {
    return function() {
        var args = Array.pototype.silce.call(argumnets);
        return function (callback) {
            args.push(callback);
            return fn.apply(this. args);
        }
    }
}

//es6
var Thunk = function(fn) {
    return function(...args) {
        return function(callback) {
            return fn.call(this, ...args, callback);
        }
    }
}

一個使用Thunk方法來實現readFile的例子

//正常版本的readFile(多參數)
fs.readFile(filename, callback);

//Thunk版本的readFile(單參數)
var readFileThunk = Thunk(filename);
readFileThunk(callback);

var Thunk = function(fileName) {
    return function(callback) {
        return fs.readFile(fileName, callback);
    }
}

能夠看到,若是咱們經過構建一個基於Thunk方法實現的runGenerators函數,能夠很好的控制咱們的generators運行流程。

function *generator() {
    var articles = yield getArticleList();
    var article = yield getArticle(articles[0].id);
    var author = yield getAuthor(article.authorId);
    console.log(author.email);
}

function runGenerator() {
    var gen = generator();
    
    function go(result) {
        if(result.done) return;
        
        result.value.then(function(rsp) {
            go(gen.next(rsp));
        })
    }
    
    go(gen.next());
}

runGenerator();

參考

相關文章
相關標籤/搜索