異步編程有哪幾種方法來實現?

這裏是修真院前端小課堂,每篇分享文從前端

【背景介紹】【知識剖析】【常見問題】【解決方案】【編碼實戰】【擴展思考】【更多討論】【參考文獻】程序員

八個方面深度解析前端知識/技能,本篇分享的是:es6

【異步編程有哪幾種方法來實現?】web

你們好,我是IT修真院武漢分院web第16期的學員孟晨,一枚正直純潔善良的web程序員 今天給你們分享一下,修真院官網js(職業)任務五,深度思考中的知識點——異步編程有哪幾種方法來實現?編程

 

1.背景介紹
你可能知道,Javascript語言的執行環境是"單線程"(single thread)。
所謂"單線程",就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。
這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。設計模式

爲了解決這個問題,Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。瀏覽器

"同步模式"就是上一段的模式,後一個任務等待前一個任務結束,而後再執行,程序的執行順序與任務的排列順序是一致的、同步的;"異步模式"則徹底不一樣,每個任務有一個或多個回調函數(callback),前一個任務結束後,不是執行後一個任務,而是執行回調函數,後一個任務則是不等前一個任務結束就執行,因此程序的執行順序與任務的排列順序是不一致的、異步的。
"異步模式"很是重要。在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操做。在服務器端,"異步模式"甚至是惟一的模式,由於執行環境是單線程的,若是容許同步執行全部http請求,服務器性能會急劇降低,很快就會失去響應。服務器

2.知識剖析
經常使用的異步編程的幾種方法?
首先咱們來看一個基本的例子,在這個例子中輸出的順序是1,3,2,咱們想讓他按順序1,2,3輸出就須要用到異步編程的方法異步

function fn1() {                                                                                     async

    console.log('Function 1')                                                                  

}                                                                                                            

function fn2() {                                                                                     

    setTimeout(() => {                                                                        

        console.log('Function 2')                                                            

    }, 2000)                                                                                       

}                                                                                                           

function fn3() {                                                                                   

    setTimeout(() => {                                                                      

        console.log('Function 3')                                                         

    }, 500)                                                                                          

}                                                                                                       

fn1()                                                                                                 

fn2()                                                                                                 

fn3()                                                                                                 

// output =>                                                                                     

// Function 1                                                                                       

// Function 3                                                                                    

// Function 2                                                                                     

 

壹.回調函數

回調函數是異步編程的方法中最簡單也是最經常使用的一個方法

採用這種方式,咱們把同步操做變成了異步操做,f1不會堵塞程序運行,至關於先執行程序的主要邏輯,將耗時的操做推遲執行。
回調函數的優勢是簡單、容易理解和部署,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,並且每一個任務只能指定一個回調函數。

若是再嵌套多幾層,代碼會變得多麼難以理解 這個被稱之爲「回調函數噩夢」(callback hell)!!!

也是能夠看看例子,若是按照我們剛剛的寫法的話輸出順序會是3,2,1,因此把每一個函數中寫成回調函數的形式

就可讓執行完了前面的纔會執行後面的,而後標紅的部分就是被稱爲回調函數噩夢的緣由,

只是少數嵌套函數的話不明顯但多層嵌套代碼就會顯得很混亂

 

function fn1(f) {

    setTimeout(() => {

        console.log('Function 1')

        f()

    }, 1000)

}

function fn2(f) {

    setTimeout(() => {

        console.log('Function 2')

        f()

    }, 2000)

}

function fn3() {

    setTimeout(() => {

        console.log('Function 3')

    }, 500)

}

fn1(function () {

    fn2(fn3)

})

// output =>

// Function 1

// Function 2

// Function 3

 

貳.事件發佈/訂閱

發佈/訂閱模式也是諸多設計模式當中的一種,剛好這種方式能夠在es5下至關優雅地處理異步操做。什麼是發佈/訂閱呢?以上一節的例子來講,fn1,fn2,fn3均可以視做一個事件的發佈者,只要執行它,就會發佈一個事件。這個時候,咱們能夠經過一個事件的訂閱者去批量訂閱並處理這些事件,包括它們的前後順序。下面咱們基於上一章節的例子,增長一個消息訂閱者的方法(爲了簡單起見,代碼使用了es6的寫法):

class AsyncFunArr {

    constructor(...arr) {

        this.funcArr = [...arr]

    }

    next() {

        const fn = this.funcArr.shift()

        if (typeof fn === 'function') fn()

    }

 

    run() {

        this.next()

    }

}

const asyncFunArr = new AsyncFunArr(fn1, fn2, fn3)

function fn1() {

console.log('Function 1')

asyncFunArr.next()

}

function fn2() {

    setTimeout(() => {

        console.log('Function 2')

        asyncFunArr.next()

    }, 500)

}

function fn3() {

    console.log('Function 3')

    asyncFunArr.next()

}

// output =>

// Function 1

// Function 2

// Function 3

 

叄.PROMISE對象

romises對象是CommonJS工做組提出的一種規範,目的是爲異步編程提供統一接口。 簡單說,它的思想是,每個異步任務返回一個Promise對象,該對象有一個then方法,容許指定回調函數。好比,f1的回調函數f2,能夠寫成:   f1().then(f2); 這樣寫的優勢在於,回調函數變成了鏈式寫法,程序的流程能夠看得很清楚,並且有一整套的配套方法,能夠實現許多強大的功能。 好比,指定多個回調函數:   f1().then(f2).then(f3); 再好比,指定發生錯誤時的回調函數:   f1().then(f2).fail(f3); 並且,它還有一個前面三種方法都沒有的好處:若是一個任務已經完成,再添加回調函數,該回調函數會當即執行。因此,你不用擔憂是否錯過了某個事件或信號。這種方法的缺點就是編寫和理解,都相對比較難。

標紅處就是所謂的鏈式寫法,這樣寫帶來告終構清晰的好處

function fn1() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('Function 1')

            resolve()

        }, 1000)

    })

}

function fn2() {

    return new Promise((resolve, reject) => {

        setTimeout(() => {

            console.log('Function 2')

            resolve()

        }, 2000)

    })

}

function fn3() {

    setTimeout(() => {

        console.log('Function 3')

    }, 500)

}

fn1()

.then(fn2)

.then(fn3)

// output =>

// Function 1

// Function 2

// Function 3

肆.GENERATOR

若是說Promise的使用可以化回調爲鏈式,那麼generator的辦法則能夠消滅那一大堆的Promise特徵方法,好比一大堆的then()。
generator函數asyncFunArr()接受一個待執行函數列表fn,異步函數將會經過yield來執行。在異步函數內,經過af.next()激活generator函數的下一步操做。
這麼粗略的看起來,其實和發佈/訂閱模式很是類似,都是經過在異步函數內部主動調用方法,告訴訂閱者去執行下一步操做。可是這種方式仍是不夠優雅,好比說若是有多個異步函數,那麼這個generator函數確定得改寫,並且在語義化的程度來講也有一點不太直觀。

function fn1() {

    setTimeout(() => {

        console.log('Function 1')

    }, 1000)

}

 

function fn2() {

    setTimeout(() => {

        console.log('Function 2')

    }, 2000)

}

 

function fn3() {

    setTimeout(() => {

        console.log('Function 3')

    }, 500)

}

 

function* asyncFunArr(...fn) {

    fn[0]()

    yield fn[1]()

    fn[2]()

}

const af = asyncFunArr(fn1, fn2, fn3)

af.next()

// output =>

// Function 1

// Function 2

// Function 3

伍.優雅的ASYNC/AWAIT

使用最新版本的Node已經能夠原生支持async/await寫法了,經過各類pollyfill也能在舊的瀏覽器使用。那麼爲何說async/await方法是最優雅的呢?
有沒有發現,在定義異步函數fn2的時候,其內容和前文使用Promise的時候如出一轍?再看執行函數asyncFunArr(),其執行的方式和使用generator的時候也很是相似。
異步的操做都返回Promise,須要順序執行時只須要await相應的函數便可,這種方式在語義化方面很是友好,對於代碼的維護也很簡單——只須要返回Promise並await它就好,無需像generator那般須要本身去維護內部yield的執行。

 

 function fn1() {

            return new Promise((resolve, reject) => {

                setTimeout(() => {

                    console.log('Function 1')

                    resolve()

                }, 3000)

            })

        }

        function fn2() {

            return new Promise((resolve, reject) => {

                setTimeout(() => {

                    console.log('Function 2')

                    resolve()

                }, 2000)

            })

        }

        function fn3() {

            setTimeout(() => {

                console.log('Function 3')

            }, 500)

        }

        async function asyncFunArr() {

            await fn1()

            await fn2()

            await fn3()

        }

        asyncFunArr()

        // output =>

        // Function 1

        // Function 2

        // Function 3

3.常見問題
什麼時候使用異步

4.解決方案
在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax操做。在服務器端,"異步模式"甚至是惟一的模式,由於執行環境是單線程的,若是容許同步執行全部http請求,服務器性能會急劇降低,很快就會失去響應。

5.代碼實戰
6.拓展思考
異步的好處: 一、異步流程能夠當即給調用方返回初步的結果。 
二、異步流程能夠延遲給調用方最終的結果數據,在此期間能夠作更多額外的工做,例如結果記錄等等。 
三、異步流程在執行的過程當中,能夠釋放佔用的線程等資源,避免阻塞,等到結果產生再從新獲取線程處理。 
四、異步流程能夠等屢次調用的結果出來後,再統一返回一次結果集合,提升響應效率。

 

 

7.參考文獻
談一談幾種處理JavaScript異步操做的辦法
Javascript異步編程的4種方法

8.更多討論
鳴謝

感謝你們觀看

BY : 孟晨

相關文章
相關標籤/搜索