JS中的幾種異步編程方式以及promise基本功能的實現(xiàn)

參考文獻

Javascript異步編程的4種方法
阮一峰ES6教程---Promise

相關(guān)技術(shù)

Promis, class, 面向?qū)ο?/p>

JS中的同步和異步

  • 同步任務(wù)模式
    在JS中,因為它的執(zhí)行時單線程的,也就是說一次只能執(zhí)行一個任務(wù),當(dāng)有多個任務(wù)需要執(zhí)行的時候,就需要排隊,一個一個的進行執(zhí)行,就像下面的數(shù)組一樣,從左到右一個一個的執(zhí)行:
    [任務(wù)一,任務(wù)二,任務(wù)三]
    這樣的任務(wù)執(zhí)行模式是同步執(zhí)行的,有可能會因為其中的某一個任務(wù)執(zhí)行時間非常長而阻塞后續(xù)任務(wù),導(dǎo)致頁面卡死;
  • 異步任務(wù)模式
    而異步任務(wù)模式則解決了上述問題,例如數(shù)組中的任務(wù)一執(zhí)行完畢后,執(zhí)行的不是數(shù)組第二位的任務(wù)二,而是一個任務(wù)二回調(diào)函數(shù)(需要將任務(wù)二做成回調(diào)函數(shù)放入任務(wù)一中),而且任務(wù)二可以不等待任務(wù)一執(zhí)行完畢就進行執(zhí)行,這種模式的任務(wù)執(zhí)行是與任務(wù)隊列(上述數(shù)組)的排列順序是不一樣的,比如下面的代碼:
// 任務(wù)一
        function fn1() {
            console.log(3)
            setTimeout(function() {
                console.log(1)
            }, 0)
        }
// 任務(wù)二
        function fn2() {
            console.log(2)
        }
        fn1()
        fn2()

在該例子中,setTimeout函數(shù)是異步的,在任務(wù)一執(zhí)行過程中遇到了setTimeout,于是將其內(nèi)的任務(wù)操作放到了事件隊列的隊尾,先去執(zhí)行任務(wù)二,執(zhí)行完畢后再回過頭來執(zhí)行隊尾的任務(wù)console.log(1),在此過程中,任務(wù)一內(nèi)的執(zhí)行沒有阻塞任務(wù)二的執(zhí)行

輸出結(jié)果


幾種異步任務(wù)模式

  • 使用setTimeout
    JS中的定時器setTimeout是實現(xiàn)異步任務(wù)執(zhí)行最簡單也最常見的方式,通過使用setTimeout(callback, 0)可以讓執(zhí)行上一個任務(wù)執(zhí)行時間過長的操作放到下一個任務(wù)執(zhí)行完之后再執(zhí)行,比如上一小節(jié)舉的例子,而關(guān)于定時器setTimeoutsetInterval相關(guān)可以參考下面鏈接:
    setTimeout
    setInterval
    優(yōu)點:簡單明了;
    缺點:①、耦合嚴重;②、容易陷入回調(diào)地獄
    PS:回調(diào)地獄例子(該操作只有三步,如果是十步,嵌套將會非常嚴重)
        function fn(callback1, callback2) {
            // 耗時操作
            let a = 0
            for (let i = 0; i < 100; i++) {
                a++
            }
            setTimeout(function() {
                callback1(++a)
                setTimeout(function() {
                    callback2(++a)
                }, 0)
            }, 0)
        }

        function fn1(a) {
            console.log(a)
        }

        function fn2(a) {
            console.log(a)
        }
        fn(fn1, fn2)
  • 發(fā)布/訂閱
    發(fā)布/訂閱模式是通過信號的發(fā)送時機來決定什么時候執(zhí)行其內(nèi)相應(yīng)的任務(wù),下面是一個發(fā)布訂閱模式的代碼實現(xiàn):
        class EventCenter {
            // 定義事件中心
            constructor() {
                this.events = {}
            }

            // 發(fā)布器
            on(evt, handler) {
                // 檢測事件信號是否存在,當(dāng)存在時不做操作,不存在時創(chuàng)建給予這個信號的方法存儲器(數(shù)組)
                this.events[evt] = this.events[evt] || []
                    // 將傳入的方法放入數(shù)組中
                this.events[evt].push({
                    handler: handler
                })
            }

            // 訂閱器
            fire(evt, params) {
                // 檢測當(dāng)前被訂閱的信號是否存在,存在則執(zhí)行其內(nèi)的所有方法
                if (!this.events[evt]) {
                    return
                }
                for (let i = 0; i < this.events[evt].length; i++) {
                    this.events[evt][i].handler(params)
                }
            }
        }

        let center = new EventCenter()

        center.on('event', function(data) {
            console.log('event執(zhí)行了第一個任務(wù)')
        })

        center.on('event', function(data) {
            console.log('event執(zhí)行了第二個任務(wù)')
        })

        center.fire('event')

輸出結(jié)果:


  • Promise
    Promise是ES6中新增的內(nèi)置對象,專門用于解決異步相關(guān)的問題,其內(nèi)最重要的兩個方法是thencatchthen方法第一個參數(shù)是resolve狀態(tài)時執(zhí)行的回調(diào),第二個參數(shù)則是reject狀態(tài)時執(zhí)行的回調(diào),而catch則是then中有一環(huán)是reject就執(zhí)行的回調(diào)函數(shù)
    通常使用的姿勢是這樣的
        function getData(){
            let promise = new Promise((resolve, reject) => {
                //  AJAX獲取數(shù)據(jù)。。。。
                if(success){
                    // 成功時執(zhí)行
                    resolve(fn1)
                }else{
                    // 失敗時執(zhí)行
                    reject(fn2)
                }
            })

            return promise
        }

        getData().then(fn1).catch(fn2)

可以從中發(fā)現(xiàn),除了thencatch兩個方法外,Promise還有鏈式調(diào)用的功能,那么下面就實現(xiàn)這樣的一個Promise。

Promise基本功能的實現(xiàn)

  • 首先寫一段測試代碼,使用Promise的調(diào)用方式
        let p = new Promise()

        function f1() {
            console.log('f1')
            setTimeout(function() {
                p.resolve('1')
            }, 1000)
            return p
        }

        function f2(result) {
            console.log('f2', result)
            setTimeout(function() {
                p.resolve('2')
            }, 1000)
        }

        function f3(result) {
            console.log('f3', result)
            setTimeout(function() {
                p.resolve('3')
            }, 1000)
        }

        function f4(result) {
            console.log('f4', result)
        }

        f1().then(f2).then(f3).catch(f4)
// 或者 f1().then(f2, f4).then(f3, f4)
  • 第一步、我們創(chuàng)建這么一個類,里面有thencatch兩個方法,并且能夠鏈式調(diào)用,為了防止重復(fù),這里用小寫開頭的promise
        class promise {
            constructor(){

            }

            then(success, fail) {
                // 鏈式調(diào)用
                return this
            }

            catch (fail) {
                // 鏈式調(diào)用
                return this
            }
        }
  • 第二步、因為有resolvereject兩種狀態(tài),那么另外再設(shè)立兩個函數(shù)resolvereject分別進行不同的狀態(tài)管理
        class promise {
            constructor() {

            }

            then(success, fail) {
                // 鏈式調(diào)用
                return this
            }

            catch (fail) {
                // 鏈式調(diào)用
                return this
            }

            // 成功狀態(tài)的管理
            resolve(result) {

            }

            // 失敗狀態(tài)的管理
            reject(result) {

            }
        }
  • 第三步、設(shè)定一個數(shù)組對需要執(zhí)行的方法進行暫存,以及一個方法的執(zhí)行器,該執(zhí)行器依賴于resolvereject傳入的狀態(tài)進行相應(yīng)的執(zhí)行
        class promise {
            constructor() {
                this.callbacks = []
            }

            then(success, fail) {
                // 鏈式調(diào)用
                return this
            }

            catch (fail) {
                // 鏈式調(diào)用
                return this
            }

            // 成功狀態(tài)的管理
            resolve(result) {
                this.actuator('resolve', result)
            }

            // 失敗狀態(tài)的管理
            reject(result) {
                this.actuator('reject', result)
            }

            // 執(zhí)行器
            actuator(status, result) {
                
            }
        }
  • 第四步、編寫then函數(shù)與執(zhí)行器中的邏輯
// 為了方便查看放到了最上面
        let p = new promise()
        function f1() {
            console.log('f1')
            setTimeout(function() {
                p.resolve('1')
            }, 1000)
            return p
        }
// ①、在寫then函數(shù)之前,先看看最開始Promise的調(diào)用方式是怎么樣的:f1().then(f2).then(f3).catch(f4),
然后在f1中嵌套回調(diào)f2并且返回這個Promise對象。
此外,then函數(shù)可以接受兩個參數(shù),一個成功回調(diào)一個失敗回調(diào),
所以思路就是創(chuàng)建一個對象里面有'resolve'和'reject'對應(yīng)這兩個回調(diào)然后放入callbacks數(shù)組中進行管理;
            then(success, fail) {
                this.callbacks.push({
                    resolve: success,
                    reject: fail
                })
                // 鏈式調(diào)用
                return this
            }


// ②、這時候在調(diào)用f1時他會先返回Promise對象,然后再調(diào)用setTimeout里面的resolve回調(diào)并傳入?yún)?shù),而在resolve函數(shù)中調(diào)用了執(zhí)行器actuator,并且傳入了resolve這個狀態(tài)和在f1中傳入的參數(shù);
            // 成功狀態(tài)的管理
            resolve(result) {
                this.actuator('resolve', result)
            }

// ③、執(zhí)行actuator函數(shù),其實分析到了這一步就很簡單了,不過是將先前傳入callbaks中的函數(shù)取出來,然后執(zhí)行其中的成功回調(diào)就是了
            actuator(status, result) {
                // 取出之前傳入的回調(diào)函數(shù)對象(包含成功和失敗回調(diào)),然后執(zhí)行
                let handlerObj = this.callbacks.shift()
                handlerObj[type](result)
            }

// ④、整體代碼

        class promise {
            constructor() {
                this.callbacks = []
            }

            then(success, fail) {
                this.callbacks.push({
                    resolve: success,
                    reject: fail
                })
                // 鏈式調(diào)用
                return this
            }

            catch (fail) {
                // 鏈式調(diào)用
                return this
            }

            // 成功狀態(tài)的管理
            resolve(result) {
                this.actuator('resolve', result)
            }

            // 失敗狀態(tài)的管理
            reject(result) {
                this.actuator('reject', result)
            }

            // 執(zhí)行器
            actuator(status, result) {
                // 取出之前傳入的回調(diào)函數(shù)對象(包含成功和失敗回調(diào)),然后執(zhí)行
                let handlerObj = this.callbacks.shift()
                handlerObj[status](result)
            }
        }

其實到了這一步,Promise的基本功能(resolve和reject)已經(jīng)實現(xiàn)了,下面來看看f1().then(f2, f4).then(f3, f4).then(f4)的執(zhí)行結(jié)果吧
①、全部resolve狀態(tài)執(zhí)行結(jié)果


②、f2為reject時候執(zhí)行結(jié)果

        function f2(result) {
            console.log('f2', result)
            setTimeout(function() {
                p.reject('2')
            }, 1000)
        }
  • 最后、我們再添加catch方法進去
    再上述代碼的基礎(chǔ)上,catch方法的實現(xiàn)其實已經(jīng)變得很簡單了,只需要在constructor里設(shè)立一個oncatch用以保存?zhèn)魅隿atch的回調(diào),然后當(dāng)中間某個環(huán)節(jié)reject的時候調(diào)用這個oncatch方法就好了,全部實現(xiàn)代碼以及調(diào)用實例:
        class promise {
            constructor() {
                    // 定義回調(diào)函數(shù)管理器和catch
                    this.callbacks = []
                    this.oncatch
                }
                // 當(dāng)狀態(tài)為reject時候,傳入reject狀態(tài)給執(zhí)行器
            reject(result) {
                    this.actuator('reject', result)
                }
                // 當(dāng)狀態(tài)為resolve時候,傳入resolve狀態(tài)給執(zhí)行器
            resolve(result) {
                    this.actuator('resolve', result)
                }
                // 執(zhí)行器
            actuator(status, result) {
                // 檢測當(dāng)狀態(tài)為reject并且oncatch不為空時,執(zhí)行oncatch保存的失敗回調(diào), 適用于f1().then(f2).then(f3).catch(f4)
                if (status === 'reject' && this.catch) {
                    this.callbacks = []
                    this.oncatch(result)
                // 檢測當(dāng)callbacks第一位有方法時,執(zhí)行相應(yīng)狀態(tài)的方法,適用于f1().then(f2, f4).then(f3, f4)
                } else if (this.callbacks[0]) {
                    let handlerObj = this.callbacks.shift()
                    if (handlerObj[status]) {
                        handlerObj[status](result)
                    }
                }
            }

            then(success, fail) {
                // 將傳入的成功和失敗回調(diào)組成對象,放入回調(diào)數(shù)組中進行管理
                this.callbacks.push({
                    resolve: success,
                    reject: fail
                })
                // 用于鏈式調(diào)用
                return this
            }

            catch (fail) {
                // 保存?zhèn)魅氲氖』卣{(diào)
                this.oncatch = fail
                // 用于鏈式調(diào)用
                return this
            }
        }


        let p = new promise()

        function f1() {
            console.log('f1')
            setTimeout(function() {
                p.resolve('1')
            }, 1000)
            return p
        }

        function f2(result) {
            console.log('f2', result)
            setTimeout(function() {
                p.resolve('2')
            }, 1000)
        }

        function f3(result) {
            console.log('f3', result)
            setTimeout(function() {
                p.resolve('3')
            }, 1000)
        }

        function f4(result) {
            console.log('f4', result)
        }
        // 第一種調(diào)用
        f1().then(f2).then(f3).catch(f4)
        // 第二種調(diào)用
        f1().then(f2, f4).then(f3, f4)

附錄,ES7中新增了await async關(guān)鍵字用于實現(xiàn)更簡單更直觀的異步代碼,甚至寫出來后和同步執(zhí)行的代碼看起來沒什么區(qū)別,但在本質(zhì)上還是使用了Promise,想要了解的同學(xué)也可以點擊下面的鏈接一起學(xué)習(xí),在此就不再多做介紹了.

體驗異步的終極解決方案-ES7的Async/Await

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內(nèi)容

  • 你不知道JS:異步 第三章:Promises 在第二章,我們指出了采用回調(diào)來表達異步和管理并發(fā)時的兩種主要不足:缺...
    purple_force閱讀 2,094評論 0 4
  • 本文適用的讀者 本文寫給有一定Promise使用經(jīng)驗的人,如果你還沒有使用過Promise,這篇文章可能不適合你,...
    HZ充電大喵閱讀 7,321評論 6 19
  • 官方中文版原文鏈接 感謝社區(qū)中各位的大力支持,譯者再次奉上一點點福利:阿里云產(chǎn)品券,享受所有官網(wǎng)優(yōu)惠,并抽取幸運大...
    HetfieldJoe閱讀 11,036評論 26 95
  • 特點 Promise能將回調(diào)分離出來,在異步操作執(zhí)行之后,用鏈式方法執(zhí)行回調(diào),雖然es5用封裝函數(shù)也能實現(xiàn),但是如...
    一二三kkxx閱讀 626評論 0 1
  • 弄懂js異步 講異步之前,我們必須掌握一個基礎(chǔ)知識-event-loop。 我們知道JavaScript的一大特點...
    DCbryant閱讀 2,744評論 0 5