1. 程式人生 > >JS異步編程 (2) - Promise、Generator、async/await

JS異步編程 (2) - Promise、Generator、async/await

bject reject 方便 異步執行 ner 是我 eject 包裝 then

上篇文章我們講了下JS異步編程的相關知識,比如什麽是異步,為什麽要使用異步編程以及在瀏覽器中JS如何實現異步的。
最後我們捎帶講了幾種JS異步編程模式(回調,事件和發布/訂閱模式),這篇我們繼續去深入了解下其他的幾種異步編程模式。

其實這幾個函數用來解決,異步中 回調函數嵌套問題 (callback hell) 回調地獄

Promise

Promise是ES6推出的一種異步編程的解決方案。其實在ES6之前,很多異步的工具庫就已經實現了各種類似的解決方案,而ES6將其寫進了語言標準,統一了用法。Promise解決了回調等解決方案嵌套的問題並且使代碼更加易讀,有種在寫同步方法的既視感。

我們先來簡單了解下ES6中Promise的用法

var p = new Promise(function async(resolve, reject){
    // 這裏是你的異步操作
    setTimeout(function(){
        if(true){
            resolve(val);
        }else{
            reject(error);
        }
    }, 1000)
})

p.then(function(val){
    console.log(‘resolve‘);
}, 
function(){ console.log(‘reject‘); })

首先,ES6規定Promise是個構造函數,其接受一個函數作為參數如上面代碼中的async函數,此函數有兩個參數,resolve、reject分別對應成功失敗兩種狀態,我們可以選擇在不同時候執行resolve或者reject去觸發下一個動作,執行then方法裏的函數。

我們可以簡單對比下回調的寫法和promise的寫法的不同

對於傳統回調寫法來說,一般會寫成這樣

asyncFn1(function () {
  asyncFn2(function() {
    asyncFn3(
function() { // xxxxx }); }); });

或者我們將各個回調函數拆出來獨立來寫以減少耦合,像是這樣:

function asyncFn1(callback) {
    return function() {
        console.log(‘asyncFn1 run‘);
        setTimeout(function(){
            callback();
        }, 1000);
    }
}

function asyncFn2(callback) {
    return function(){
        console.log(‘asyncFn2 run‘);
        setTimeout(function(){
            callback();
        }, 1000);
    }
}

function normalFn3() {
    console.log(‘normalFn3 run‘);
}

asyncFn1(asyncFn2(normalFn3))()

最後我們看下Promise的寫法

function asyncFn1() {
    console.log(‘asyncFn1 run‘);
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve();
        }, 1000)
    })
}

function asyncFn2() {
    console.log(‘asyncFn2 run‘);
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve();
        }, 1000)
    })
}

function normalFn3() {
    console.log(‘normalFn3 run‘);
}

asyncFn1().then(asyncFn2).then(normalFn3);

這樣來看無論是第一種還是第二種寫法,都會讓人感到不是很直觀,而Promise的寫法更加直觀和語義化。

Generator

Generator函數也是ES6提供的一種特殊的函數,其語法行為與傳統函數完全不同。

我們先直觀看個Generator實際的用法

function* oneGenerator() {
  yield ‘Learn‘;
  yield ‘In‘;
  return ‘Pro‘;
}

var g = oneGenerator();

g.next();   // {value: "Learn", done: false}
g.next();   // {value: "In", done: false}
g.next();   // {value: "Pro", done: true}

Generator函數是一種特殊的函數,他有這麽幾個特點:

  • 聲明時需要在function後面加上*,並且配合函數裏面yield關鍵字來使用。

  • 在執行Generator函數的時候,其會返回一個Iterator遍歷器對象,通過其next方法,將Generator函數體內的代碼以yield為界分步執行

  • 具體來說當執行Generator函數時,函數並不會執行,而是需要調用Iterator遍歷器對象的next方法,這時程序才會執行從頭或者上一個yield之後到下一個yield或者return或者函數體尾部之間的代碼,並且將yield後面的值,包裝成json對象返回。就像上面的例子中的{value: xxx, done: xxx}

  • value取的yield或者return後面的值,否則就是undefined,done的值如果碰到return或者執行完成則返回true,否則返回false。

我們知道了簡單的Generator函數的用法以後,我們來看下如何使用Generator函數進行異步編程。

首先我們先來看下使用Generator函數能達到怎樣的效果。

// 使用Generator函數進行異步編程
function* oneGenerator() {
  yield asyncFn1();
  yield asyncFn2();
  yield normalFn3();
}

// 我們來對比一下Promise
asyncFn1().then(asyncFn2).then(normalFn3);

我們可以看出使用Generator函數進行異步編程更像是在寫同步任務,對比Promise少了很多次then方法的調用。

好,那麽接下來我們就來看下如何實際使用Generator函數進行異步編程。

這裏我要特別說明一下,事實上Generator函數不像Promise一樣是專門用來解決異步處理而產生的,人們只是使用其特性來產出了一套異步的解決方案,所以使用Generator並不像使用Promise一樣有一種開箱即用的感覺。其更像是在Promise或者回調這類的解決方案之上又封裝了一層,讓你可以像上面例子裏一樣去那麽寫。

我們還是具體來看下上面的例子,我們知道單寫一個Generator是不能運行的對吧,我們需要執行他並且使用next方法來讓他分步執行,那麽什麽時候去調用next呢?答案就是我們需要在異步完成時去調用next。我們來按照這個思路補全上面的例子。

var g;

function asyncFn() {
    setTimeout(function(){
        g.next();
    }, 1000)
}

function normalFn() {
    console.log(‘normalFn run‘);
}

function* oneGenerator() {
  yield asyncFn();
  return normalFn();
}

g = oneGenerator();

g.next();

// 這裏在我調用next方法的時候執行了asyncFn函數
// 然後我們的希望是在異步完成時自動去再調用g.next()來進行下面的操作,所以我們必須在上面asyncFn函數體內的寫上g.next(); 這樣才能正常運行。

// 但其實這樣是比較奇怪的,因為當我定義asyncFn的時候其實是不知道oneGenerator執行後叫什麽名兒的,即使我們提前約定叫g,但這樣asyncFn就太過於耦合了,不僅寫法很奇怪而且耦合太大不利於擴展和重用。反正總而言之這種寫法很不好。

那麽怎麽解決呢,我們需要自己寫個方法,能自動運行Generator函數,這種方法很簡單在社區裏有很多,最著名的就是大神TJ寫的co模塊,有興趣的同學可以看下其源碼實現。這裏我們簡單造個輪子:

// 如果我們想要去在異步執行完成時自動調用next就需要有一個鉤子,回調函數的callback或者Promise的then。

function autoGenerator(generator){
  var g = generator();

  function next(){
    var res = g.next();  // {value: xxx, done: xxx}

    if (res.done) {
        return res.value;
    }

    if(typeof res.value === ‘function‘){    // 認為是回調
        res.value(next);
    }else if(typeof res.value === ‘object‘ && typeof res.value.then === ‘function‘){     // 認為是promise
        res.value.then(function(){
            next();
        })
    }else{
        next();
    }
  }

  next();
}

// ----
function asyncFn1(){
    console.log(‘asyncFn1‘);
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve();
        }, 1000)
    })
}

function asyncFn2() {
    console.log(‘asyncFn2‘);
    return function(callback){
        setTimeout(function(){
            callback();
        }, 1000);
    }
}

function normalFn() {
    console.log(‘normalFn‘);
}

function* oneGenerator() {
  yield asyncFn1();
  yield asyncFn2();
  yield normalFn();
}

autoGenerator(oneGenerator);

這個方法我們簡單實現了最核心的部分,有些判斷可能並不嚴謹,但大家理解這個思路就可以了。有了這個方法,我們才可以方便的使用Generator函數進行異步編程。

Async/Await

如果你學會了Generator函數,對於Async函數就會很容易上手。你可以簡單把Async函數理解成就是Generator函數+執行器。我們就直接上實例好了

function asyncFn1(){
    console.log(‘asyncFn1‘);
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve(‘123‘);
        }, 2000)
    })
}

function asyncFn2() {
    console.log(‘asyncFn2‘);
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve(‘456‘);
        }, 2000)
    })
}

async function asyncFn () {
    var a = await asyncFn1();
    var b = await asyncFn2();

    console.log(a,b)
}

asyncFn();

// asyncFn1
// asyncFn2
// 123,456

當然async裏實現的執行器肯定是跟咱們上面簡單實現的有所不同,所以在用法上也會有些註意的點

  • 首先async函數的返回值是一個Promise對象,不像是generator函數返回的是Iterator遍歷器對象,所以async函數執行後可以繼續使用then等方法來繼續進行下面的邏輯

  • await後面一般跟Promise對象,async函數執行時,遇到await後,等待後面的Promise對象的狀態從pending變成resolve的後,將resolve的參數返回並自動往下執行直到下一個await或者結束

  • await後面也可以跟一個async函數進行嵌套使用。

對於異步來說,還有很多的知識點我們沒有講到,比如異常處理,多異步並行執行等等,這篇和上篇文章主要還是希望大家對異步編程有個直觀的了解,清楚各種解決方案之間的區別和優劣。

轉 : https://www.cnblogs.com/learninpro/p/9271813.html

JS異步編程 (2) - Promise、Generator、async/await