JS異步編程中的回調(diào)與promise

最近抽空復(fù)習(xí)了一下之前讀過的JS書,看了一下關(guān)于回調(diào)函數(shù)和promise相關(guān)部分。

回調(diào)函數(shù)

提到異步編程,盡管發(fā)展到如今,js中解決異步的方式已經(jīng)出現(xiàn)了很多種,Promise、async/await... 但不可否認(rèn),在這些出現(xiàn)之前,我們采用的最常規(guī)的方式就是回調(diào)函數(shù),可以說,回調(diào)函數(shù)是js中最基礎(chǔ)的異步模式。但盡管如此,回調(diào)還是存在著很多不可忽視的缺點(diǎn)。

  • 執(zhí)行順序

思考這樣一段代碼

fs.readFile('file.txt', 'utf-8', functino(data){
    console.log('A');
    setTimeout(function () {
        console.log('B')
    }, 0)
    console.log('C')
})
console.log('D'); 

有經(jīng)驗(yàn)的同學(xué)可能稍加推敲就能得出正確結(jié)論,

D A C B

但不可置否,這樣一段代碼的執(zhí)行順序是違背我們大腦的正常思維順序的,我們?cè)诖竽X中是不斷上下跳躍著的。再有,如果把上面的setTimeout換成一個(gè)同步函數(shù)呢?那么結(jié)果就是D A B C。再如果它只是會(huì)視情況而定同步或者異步,也就是我們并不確定它是同步還是異步,這樣的情況下,我們?nèi)绾谓鉀Q呢?

解決方法或許只能將每個(gè)步驟硬編碼到前一個(gè)步驟中了。

但是上述只是個(gè)簡(jiǎn)單例子,現(xiàn)實(shí)中的項(xiàng)目遠(yuǎn)比這個(gè)復(fù)雜,嵌套的更深,狀態(tài)更多,
這種方式使得代碼可復(fù)用性變差,維護(hù)成本變高,與我們現(xiàn)在提倡的低耦合相駁。

  • 控制反轉(zhuǎn)
// 假如doSomeThing()是一個(gè)第三方api,負(fù)責(zé)做某些事情
// 通過傳一個(gè)callback來執(zhí)行接下來的步驟
doSomeThing('...', function () {
    // ...
})

上述例子中, callback的執(zhí)行取決于doSomeThing(),這種現(xiàn)象叫做"控制反轉(zhuǎn)",如果doSomeThing中發(fā)生異常,或者說doSomeThing是一個(gè)你根本不了解的第三方api,那么你所傳的callback可能出現(xiàn)任何你想不到的情況,因?yàn)榇藭r(shí)callback的控制權(quán)并不在你手中, 你不能決定它何時(shí)調(diào)用,調(diào)用次數(shù),是否傳參等等等等....

引用《你不知道的JavaScript中卷》

回調(diào)最大的問題是控制反轉(zhuǎn),它會(huì)導(dǎo)致信任鏈的完全斷裂。

總而言之,我們需要一種比回調(diào)更好的機(jī)制,來解決執(zhí)行順序、信任的問題。值得欣喜的是,JS目前已經(jīng)提供了很多更加強(qiáng)大的異步模式,Promise就是其中之一。

Promise

所謂 Promise,就是一個(gè)對(duì)象,用來傳遞異步操作的消息。它代表了某個(gè)未來才會(huì)知道結(jié)果的事件(通常是一個(gè)異步操作),并且這個(gè)事件提供統(tǒng)一的 API,可供進(jìn)一步處理。

Promise 對(duì)象有以下兩個(gè)特點(diǎn)。

  • 對(duì)象的狀態(tài)不受外界影響。Promise 對(duì)象代表一個(gè)異步操作,有三種狀態(tài):Pending(進(jìn)行中)、Resolved(已完成,又稱 Fulfilled)和 Rejected(已失?。V挥挟惒讲僮鞯慕Y(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個(gè)狀態(tài)。這也是 Promise 這個(gè)名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。

  • 一旦狀態(tài)改變,就不會(huì)再變,任何時(shí)候都可以得到這個(gè)結(jié)果。Promise 對(duì)象的狀態(tài)改變,只有兩種可能:從 Pending 變?yōu)?Resolved 和從 Pending 變?yōu)?Rejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會(huì)再變了,會(huì)一直保持這個(gè)結(jié)果。就算改變已經(jīng)發(fā)生了,你再對(duì) Promise 對(duì)象添加回調(diào)函數(shù),也會(huì)立即得到這個(gè)結(jié)果。這與事件(Event)完全不同,事件的特點(diǎn)是,如果你錯(cuò)過了它,再去監(jiān)聽,是得不到結(jié)果的。

基本用法

// 我們定義三個(gè)異步行為A、B、C
function A (cb) {
    setTimeout(function () {
        console.log('執(zhí)行A')
        cb && cb()
    })
}
function B (cb) {
    setTimeout(function () {
        console.log('執(zhí)行B')
        cb && cb()
    })
}
function C (cb) {
    setTimeout(function () {
        console.log('執(zhí)行C')
        cb && cb()
    })
    
}

假設(shè)這三個(gè)行為是相互依賴關(guān)系執(zhí)行,也就是A執(zhí)行完再執(zhí)行B,B執(zhí)行完再執(zhí)行C
首先看es5的實(shí)現(xiàn)方式

    A(B(C))

在看Promise版本

function A () {
    return new Promise((resolve, reject) => {
        setTimeout(function () {
            console.log('執(zhí)行A')
            resolve()
        })
    })
}
function B () {
    return new Promise((resolve, reject) => {
        setTimeout(function () {
            console.log('執(zhí)行B')
            resolve()
        })
    })
}
function C () {
    return new Promise((resolve, reject) => {
        setTimeout(function () {
            console.log('執(zhí)行C')
            resolve()
        })
    })
}


A().then(B).then(C);

怎么樣,是不是覺得清晰了很多?

回想一下我們?cè)谏厦婊卣{(diào)函數(shù)中遇到的兩個(gè)問題 執(zhí)行順序控制反轉(zhuǎn)

  • 執(zhí)行順序

我們可以看到 在promise中我們可以很清晰的看出來,先執(zhí)行A接下來是B然后是C,并且我們也不需要關(guān)心A或者B中是同步還是異步操作,無論同步異步都不會(huì)影響到執(zhí)行順序。
這種方式使得我們的代碼一眼就可以看清楚他的執(zhí)行流程,無論維護(hù)成本還是清晰程度都比回調(diào)函數(shù)要好的多,避免了“Callback Hell(回調(diào)地獄)”

  • 控制反轉(zhuǎn)

Promise擁有個(gè)then方法,then方法的第一個(gè)參數(shù)是resolved狀態(tài)的回調(diào)函數(shù),第二個(gè)參數(shù)(可選)是rejected狀態(tài)的回調(diào)函數(shù)。我們可以根據(jù)promise的狀態(tài),如果為resolved,就調(diào)用第一個(gè)回調(diào)函數(shù),如果狀態(tài)變?yōu)閞ejected,就調(diào)用第二個(gè)回調(diào)函數(shù)。這樣我們相當(dāng)于把控制權(quán)重新拿回到我們自己手中。
舉個(gè)例子

function A () {
    return new Promise((resolve, reject) => {
        setTimeout(function () {
            console.log('執(zhí)行A')
            resolve('a')
        })
    })
}

A().then(function(data){
    // data就是A返回的proise狀態(tài)成功后所返回的值
    console.log(data); // 'a'
}, function(err) {
    // 如果A的狀態(tài)變?yōu)閞eject,將會(huì)處罰這個(gè)回調(diào)函數(shù)
})


除了then之外,promise還有幾個(gè)方法。

Promise.prototype.catch();

Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)

promiseFn.then(function(posts) {
  // ...
}).catch(function(error) {
  // 處理 promiseFn 和 前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤
  console.log('發(fā)生錯(cuò)誤!', error);
});

Promise.all()

Promise.all()用于將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。

const p = Promise.all([p1, p2, p3]);

返回的結(jié)果是一個(gè)數(shù)組,里面對(duì)應(yīng)參數(shù)中的幾個(gè)promise實(shí)例的返回值。
只有當(dāng)這幾個(gè)實(shí)例的狀態(tài)都變成成功,或者其中有一個(gè)變?yōu)槭?,才?huì)調(diào)用Promise.all方法后面的回調(diào)函數(shù)。

Promise.race()

Promise.race()方法同樣是將多個(gè) Promise 實(shí)例,包裝成一個(gè)新的 Promise 實(shí)例。

const p = Promise.race([p1, p2, p3]);

但是不同于Promise.all的是,只要p1、p2、p3之中有一個(gè)實(shí)例率先改變狀態(tài),p的狀態(tài)就跟著改變。那個(gè)率先改變的 Promise 實(shí)例的返回值,就傳遞給p的回調(diào)函數(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 弄懂js異步 講異步之前,我們必須掌握一個(gè)基礎(chǔ)知識(shí)-event-loop。 我們知道JavaScript的一大特點(diǎn)...
    DCbryant閱讀 2,869評(píng)論 0 5
  • 前言 編程語言很多的新概念都是為了更好的解決老問題而提出來的。這篇博客就是一步步分析異步編程解決方案的問題以及后續(xù)...
    李向_c52d閱讀 1,163評(píng)論 0 2
  • Promise 是異步編程的一種解決方案。簡(jiǎn)單來說 Promise 就是一個(gè)容器,里面保存著某個(gè)未來才會(huì)結(jié)束的異步...
    點(diǎn)融黑幫閱讀 854評(píng)論 0 13
  • 你不知道JS:異步 第三章:Promises 在第二章,我們指出了采用回調(diào)來表達(dá)異步和管理并發(fā)時(shí)的兩種主要不足:缺...
    purple_force閱讀 2,226評(píng)論 0 4
  • 參考深入理解 Promise 五部曲 -- 1.異步問題[http://www.ghostchina.com/pr...
    合肥黑閱讀 2,430評(píng)論 0 14

友情鏈接更多精彩內(nèi)容