Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promise, generator, async 與 ES6 #2

Open
aszx87410 opened this issue Dec 3, 2018 · 0 comments
Open

Promise, generator, async 與 ES6 #2

aszx87410 opened this issue Dec 3, 2018 · 0 comments
Labels
JS JavaScript

Comments

@aszx87410
Copy link
Owner

在Javascript裡面,有一個超級重要的概念就是非同步,這也是剛入門的時候最容易搞混、忘記的觀念
ES6原生支援了Promise,搭配Generator使用效果更佳,而ES7甚至支援了async的語法
我覺得這算是一個演進的過程,讓程式架構越來越好、可讀性越來越高
所以要講解這些新的東西,就先從最基本的callback開始吧

現在假設我們有三個api
第一個是抓取文章列表的api

[
  {
    "title": "文章1",
    "id": 1
  },
  {
    "title": "文章2",
    "id": 2
  },
  {
    "title": "文章3",
    "id": 3
  }
]

第二個是給文章id, 抓取文章內容的api

{
  "authorId": 5,
  "content": "content",
  "timestamp": "2015-08-26"
}

第三個是給作者id, 返回作者資訊的api

{
  "email": "aszx87410@gmail.com",
  "name": "huli",
  "id": 5
}

現在想要達成的功能是:抓取最新的一篇文章的作者信箱
流程就是:抓文章列表->抓文章資訊->抓作者
實作成code就像這樣

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);
    });
}

或可參考線上範例:用callback實做

相信這樣子的code大家應該都不陌生,但是這樣會有一個缺點
就是我們俗稱的callback hell,這樣一層一層一層的實在是有點醜
那該怎麼辦呢?有種東西叫做Promise,就這樣出現了
先來個實際範例再來講解吧!

getArticleList().then(function(articles){
	return getArticle(articles[0].id);
}).then(function(article){
    return getAuthor(article.authorId);
}).then(function(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);
        }); 
    });
}

線上範例:用Promise實做
Promise是一個物件,有三種狀態,等待中(pending)、完成(resolve or fulfilled)跟失敗(reject)
在上面的範例中,我們把那三個函數的callback function拿掉了,取而代之的是返回一個Promise物件
原本應該是callback要出現的地方,變成了resolve
這樣有什麼好處呢?看我們最上面呼叫這些函式的地方,原本的callback hell不見了,被我們壓平了
如果看的不是很懂,就先從最基本的,呼叫一個Promise開始

getArticleList().then(function(articles){
  console.log(articles);
});

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

你可以在Promise物件後面加上.then,就會是這個Promise跑完之後的結果
假如在then裡面return另一個Promise物件,就可以不斷串接使用
像是這樣

getArticleList().then(function(articles){
	return getArticle(articles[0].id);
}).then(function(article){
    return getAuthor(article);
});

有了Promise的這個特性,就可以避免掉callback hell
如果我們加上ES6的arrow function,甚至可以簡化成這樣

getArticleList()
.then(articles => getArticle(articles[0].id))
.then(article => getAuthor(article.authorId))
.then(author => {
	alert(author.email);
});

線上範例 Promise+arrow function

單純運用Promise的範例就到這邊為止,其實到這邊語法已經滿簡單的了,而且有了arrow function以後,可讀性有變得比較好,但是看到一堆then總是覺得有點礙眼

那接下來還有什麼呢?
ES6裡面多了一個Generator,如果不知道的話可參考我的上一篇文章[Javascript] ES6 Generator基礎
接著就要利用Generator的特性,來寫出超級像同步但其實是非同步的程式碼

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");
      })
  })
});

完整版線上範例 Promise + Generator

仔細看run這個generator,利用yield的特性,會先執行右邊的程式碼,等待下一次的呼叫並且賦值給左邊
所以我們可以在getArticleList()裡面的then事件呼叫gen.next(r1),就會把回傳值丟給articles這個變數
如果覺得這樣有點難懂,可以先換成只有一層的

function* run(){
  var articles = yield getArticleList();
  console.log(articles); 
}

var gen = run();

//第一次呼叫,會執行到getArticleList(),會回傳一個Promise
gen.next().value.then(function(r1){

  //第一個Promise結束後,把r1丟回給generator,讓articles = getArticleList()的回傳值
  gen.next(r1);
  console.log('done');
});

讓我們再回來看看上面那段程式碼的上半部

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

有沒有覺得,跟同步的程式碼很像?只要把yield拿掉的話,根本就一模一樣對吧!
這就是generator的精髓所在了:用很像同步的語法,但其實是非同步

那再來看看下半部

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");
      })
  })
});

很容易可以發現下半部的語法很固定,並且容易找出規律
而且根本就是個遞迴
所以可以用一個函式包住,處理更多general的case

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

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

runGenerator();

完整版線上範例 Promise + Generator + 遞迴
tj做的co模組就是在做差不多的事情,只是做得更多了
但原理跟我們上面寫的runGenerator很類似,就是把一個generator包起來寫一個自動執行器

最後,終於要講到標題上的最後一個東西了:async
這是什麼?先來看code

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

完整版線上範例 async(沒辦法跑)
(jsfiddle支援度沒那麼高,所以沒辦法跑這段code)

這段code跟之前的差別在於

  1. function* gen()變成async function run()
  2. yield變成await
    就這兩個點而已
    然後你會發現,就這樣就結束了
    不必用其他模組,不必自己寫遞迴執行器
    這就是async的語法,其實就是把那些自動執行寫好而已,但是這樣的語法讓我們方便許多
    而其實這個語法是在ES7才有計畫引入的,QQ

好消息是,我們上面有關ES6的code都是通過babel這個library轉換成ES5的語法
而他有個實驗性功能的地方,其中就有包含async
async是在stage 2NOTE: Stage 2 and above are enabled by default.
什麼參數都不用調整就自動幫你開啟了,真是可喜可賀

當初剛接觸ES6時,一下子接觸到一堆眼花撩亂的東西,每個再繼續深入下去都是一門學問
而且我在之前都是純粹用callback(因為層級不多所以還好),偶爾用一下async(node的library,跟上面的不一樣)
所以我覺得最好了解的方式,就是從最基礎的callback開始,慢慢進步到promise,再進步到generator,最後才是async
才能懂得為什麼會有這些東西的出現

如果有哪些地方有講錯,還麻煩留個言或是寄信給我
感謝

ref:
ECMAScript 6 入门 异步操作
JavaScript Promises
拥抱Generator,告别异步回调
深入浅出ES6(三):生成器 Generators

@aszx87410 aszx87410 added the JS JavaScript label Dec 3, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JS JavaScript
Projects
None yet
Development

No branches or pull requests

1 participant