使用Promise解決多層異步調(diào)用的簡(jiǎn)單學(xué)習(xí)心得
來(lái)源:易賢網(wǎng) 閱讀:942 次 日期:2016-06-27 16:01:55
溫馨提示:易賢網(wǎng)小編為您整理了“使用Promise解決多層異步調(diào)用的簡(jiǎn)單學(xué)習(xí)心得”,方便廣大網(wǎng)友查閱!

下面小編就為大家?guī)?lái)一篇使用Promise解決多層異步調(diào)用的簡(jiǎn)單學(xué)習(xí)心得。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。

前言

第一次接觸到Promise這個(gè)東西,是2012年微軟發(fā)布Windows8操作系統(tǒng)后抱著作死好奇的心態(tài)研究用html5寫(xiě)Metro應(yīng)用的時(shí)候。當(dāng)時(shí)配合html5提供的WinJS庫(kù)里面的異步接口全都是Promise形式,這對(duì)那時(shí)候剛剛畢業(yè)一點(diǎn)javascript基礎(chǔ)都沒(méi)有的我而言簡(jiǎn)直就是天書(shū)。我當(dāng)時(shí)想的是,微軟又在腦洞大開(kāi)的瞎搗鼓了。

結(jié)果沒(méi)想到,到了2015年,Promise居然寫(xiě)進(jìn)ES6標(biāo)準(zhǔn)里面了。而且一項(xiàng)調(diào)查顯示,js程序員們用這玩意用的還挺high。

諷刺的是,作為早在2012年就在Metro應(yīng)用開(kāi)發(fā)接口里面廣泛使用Promise的微軟,其自家瀏覽器IE直到2015年壽終正寢了都還不支持Promise,看來(lái)微軟不是沒(méi)有這個(gè)技術(shù),而是真的對(duì)IE放棄治療了。。。

現(xiàn)在回想起來(lái),當(dāng)時(shí)看到Promise最頭疼的,就是初學(xué)者看起來(lái)匪夷所思,也是最被js程序員廣為稱道的特性:then函數(shù)調(diào)用鏈。

then函數(shù)調(diào)用鏈,從其本質(zhì)上而言,就是對(duì)多個(gè)異步過(guò)程的依次調(diào)用,本文就從這一點(diǎn)著手,對(duì)Promise這一特性進(jìn)行研究和學(xué)習(xí)。

Promise解決的問(wèn)題

考慮如下場(chǎng)景,函數(shù)延時(shí)2秒之后打印一行日志,再延時(shí)3秒打印一行日志,再延時(shí)4秒打印一行日志,這在其他的編程語(yǔ)言當(dāng)中是非常簡(jiǎn)單的事情,但是到了js里面就比較費(fèi)勁,代碼大約會(huì)寫(xiě)成下面的樣子:

var myfunc = function() {  

  setTimeout(function() {

    console.log("log1");

    setTimeout(function() {

      console.log("log2");

      setTimeout(function() {

        console.log("log3");

      }, 4000);

    }, 3000); 

  }, 2000);

}

由于嵌套了多層回調(diào)結(jié)構(gòu),這里形成了一個(gè)典型的金字塔結(jié)構(gòu)。如果業(yè)務(wù)邏輯再?gòu)?fù)雜一些,就會(huì)變成令人聞風(fēng)喪膽的回調(diào)地獄。

如果意識(shí)比較好,知道提煉出簡(jiǎn)單的函數(shù),那么代碼差不多是這個(gè)樣子:

var func1 = function() {

  setTimeout(func2, 2000);

};

var func2 = function() {

  console.log("log1");

  setTimeout(func3, 3000);

};

var func3 = function() {

  console.log("log2");

  setTimeout(func4, 4000);

};

var func4 = function() {

  console.log("log3");

};

這樣看起來(lái)稍微好一點(diǎn)了,但是總覺(jué)得有點(diǎn)怪怪的。。。好吧,其實(shí)我js水平有限,說(shuō)不上來(lái)為什么這樣寫(xiě)不好。如果你知道為什么這樣寫(xiě)不太好所以發(fā)明了Promise,請(qǐng)告訴我。

現(xiàn)在讓我們言歸正傳,說(shuō)說(shuō)Promise這個(gè)東西。

Promise的描述

這里請(qǐng)?jiān)试S我引用MDN對(duì)Promise的描述:

Promise 對(duì)象用于延遲(deferred) 計(jì)算和異步(asynchronous ) 計(jì)算.。一個(gè)Promise對(duì)象代表著一個(gè)還未完成,但預(yù)期將來(lái)會(huì)完成的操作。

Promise 對(duì)象是一個(gè)返回值的代理,這個(gè)返回值在promise對(duì)象創(chuàng)建時(shí)未必已知。它允許你為異步操作的成功或失敗指定處理方法。 這使得異步方法可以像同步方法那樣返回值:異步方法會(huì)返回一個(gè)包含了原返回值的 promise 對(duì)象來(lái)替代原返回值。

Promise對(duì)象有以下幾種狀態(tài):

•pending: 初始狀態(tài), 非 fulfilled 或 rejected。

•fulfilled: 成功的操作。

•rejected: 失敗的操作。

pending狀態(tài)的promise對(duì)象既可轉(zhuǎn)換為帶著一個(gè)成功值的fulfilled 狀態(tài),也可變?yōu)閹е粋€(gè)失敗信息的 rejected 狀態(tài)。當(dāng)狀態(tài)發(fā)生轉(zhuǎn)換時(shí),promise.then綁定的方法(函數(shù)句柄)就會(huì)被調(diào)用。(當(dāng)綁定方法時(shí),如果 promise對(duì)象已經(jīng)處于 fulfilled 或 rejected 狀態(tài),那么相應(yīng)的方法將會(huì)被立刻調(diào)用, 所以在異步操作的完成情況和它的綁定方法之間不存在競(jìng)爭(zhēng)條件。)

更多關(guān)于Promise的描述和示例可以參考MDN的Promise條目,或者M(jìn)SDN的Promise條目。

嘗試使用Promise解決我們的問(wèn)題

基于以上對(duì)Promise的了解,我們知道可以使用它來(lái)解決多層回調(diào)嵌套后的代碼蠢笨難以維護(hù)的問(wèn)題。關(guān)于Promise的語(yǔ)法和參數(shù)上面給出的兩個(gè)鏈接已經(jīng)說(shuō)的很清楚了,這里不重復(fù),直接上代碼。

我們先來(lái)嘗試一個(gè)比較簡(jiǎn)單的情況,只執(zhí)行一次延時(shí)和回調(diào):

new Promise(function(res, rej) {

  console.log(Date.now() + " start setTimeout");

  setTimeout(res, 2000);

}).then(function() {

  console.log(Date.now() + " timeout call back");

});

看起來(lái)和MSDN里的示例也沒(méi)什么區(qū)別,執(zhí)行結(jié)果如下:

$ node promisTest.js

1450194136374 start setTimeout

1450194138391 timeout call back

那么如果我們要再做一個(gè)延時(shí)呢,那么我可以這樣寫(xiě):

new Promise(function(res, rej) {

  console.log(Date.now() + " start setTimeout 1");

  setTimeout(res, 2000);

}).then(function() {

  console.log(Date.now() + " timeout 1 call back");

  new Promise(function(res, rej) {

    console.log(Date.now() + " start setTimeout 2");

    setTimeout(res, 3000);

  }).then(function() {

    console.log(Date.now() + " timeout 2 call back");

  })

});

似乎也能正確運(yùn)行:

$ node promisTest.js

1450194338710 start setTimeout 1

1450194340720 timeout 1 call back

1450194340720 start setTimeout 2

1450194343722 timeout 2 call back

不過(guò)代碼看起來(lái)蠢萌蠢萌的是不是,而且隱約又在搭金字塔了。這和引入Promise的目的背道而馳。

那么問(wèn)題出在哪呢?正確的姿勢(shì)又是怎樣的?

答案藏在then函數(shù)以及then函數(shù)的onFulfilled(或者叫onCompleted)回調(diào)函數(shù)的返回值里面。

首先明確的一點(diǎn)是,then函數(shù)會(huì)返回一個(gè)新的Promise變量,你可以再次調(diào)用這個(gè)新的Promise變量的then函數(shù),像這樣:

new Promise(...).then(...)

  .then(...).then(...).then(...)...

而then函數(shù)返回的是什么樣的Promies,取決于onFulfilled回調(diào)的返回值。

事實(shí)上,onFulfilled可以返回一個(gè)普通的變量,也可以是另一個(gè)Promise變量。

如果onFulfilled返回的是一個(gè)普通的值,那么then函數(shù)會(huì)返回一個(gè)默認(rèn)的Promise變量。執(zhí)行這個(gè)Promise的then函數(shù)會(huì)使Promise立即被滿足,執(zhí)行onFulfilled函數(shù),而這個(gè)onFulfilled的入?yún)?,即是上一個(gè)onFulfilled的返回值。

而如果onFulfilled返回的是一個(gè)Promise變量,那個(gè)這個(gè)Promise變量就會(huì)作為then函數(shù)的返回值。

關(guān)于then函數(shù)和onFulfilled函數(shù)的返回值的這一系列設(shè)定,MDN和MSDN上的文檔都沒(méi)有明確的正面描述,至于ES6官方文檔ECMAScript 2015 (6th Edition, ECMA-262)。。。我的水平有限實(shí)在看不懂,如果哪位高手能解釋清楚官方文檔里面對(duì)著兩個(gè)返回值的描述,請(qǐng)一定留言指教?。?!

所以以上為我的自由發(fā)揮,語(yǔ)言組織的有點(diǎn)拗口,上代碼看一下大家就明白了。

首先是返回普通變量的情況:

new Promise(function(res, rej) {

  console.log(Date.now() + " start setTimeout 1");

  setTimeout(res, 2000);

}).then(function() {

  console.log(Date.now() + " timeout 1 call back");

  return 1024;

}).then(function(arg) {

  console.log(Date.now() + " last onFulfilled return " + arg);  

});

以上代碼執(zhí)行結(jié)果為:

$ node promisTest.js

1450277122125 start setTimeout 1

1450277124129 timeout 1 call back

1450277124129 last onFulfilled return 1024

有點(diǎn)意思對(duì)不對(duì),但這不是關(guān)鍵。關(guān)鍵是onFulfilled函數(shù)返回一個(gè)Promise變量可以使我們很方便的連續(xù)調(diào)用多個(gè)異步過(guò)程。比如我們可以這樣來(lái)嘗試連續(xù)做兩個(gè)延時(shí)操作:

new Promise(function(res, rej) {

  console.log(Date.now() + " start setTimeout 1");

  setTimeout(res, 2000);

}).then(function() {

  console.log(Date.now() + " timeout 1 call back");

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start setTimeout 2");

    setTimeout(res, 3000);

  });

}).then(function() {

  console.log(Date.now() + " timeout 2 call back");

});

執(zhí)行結(jié)果如下:

$ node promisTest.js

1450277510275 start setTimeout 1

1450277512276 timeout 1 call back

1450277512276 start setTimeout 2

1450277515327 timeout 2 call back

如果覺(jué)得這也沒(méi)什么了不起,那再多來(lái)幾次也不在話下:

new Promise(function(res, rej) {

  console.log(Date.now() + " start setTimeout 1");

  setTimeout(res, 2000);

}).then(function() {

  console.log(Date.now() + " timeout 1 call back");

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start setTimeout 2");

    setTimeout(res, 3000);

  });

}).then(function() {

  console.log(Date.now() + " timeout 2 call back");

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start setTimeout 3");

    setTimeout(res, 4000);

  });

}).then(function() {

  console.log(Date.now() + " timeout 3 call back");

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start setTimeout 4");

    setTimeout(res, 5000);

  });

}).then(function() {

  console.log(Date.now() + " timeout 4 call back");

});

$ node promisTest.js

1450277902714 start setTimeout 1

1450277904722 timeout 1 call back

1450277904724 start setTimeout 2

1450277907725 timeout 2 call back

1450277907725 start setTimeout 3

1450277911730 timeout 3 call back

1450277911730 start setTimeout 4

1450277916744 timeout 4 call back

可以看到,多個(gè)延時(shí)的回調(diào)函數(shù)被有序的排列下來(lái),并沒(méi)有出現(xiàn)喜聞樂(lè)見(jiàn)的金字塔狀結(jié)構(gòu)。雖然代碼里面調(diào)用的都是異步過(guò)程,但是看起來(lái)就像是全部由同步過(guò)程構(gòu)成的一樣。這就是Promise帶給我們的好處。

如果你有把啰嗦的代碼提煉成單獨(dú)函數(shù)的好習(xí)慣,那就更加畫(huà)美不看了:

function timeout1() {

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start timeout1");

    setTimeout(res, 2000);

  });

}

function timeout2() {

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start timeout2");

    setTimeout(res, 3000);

  });

}

function timeout3() {

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start timeout3");

    setTimeout(res, 4000);

  });

}

function timeout4() {

  return new Promise(function(res, rej) {

    console.log(Date.now() + " start timeout4");

    setTimeout(res, 5000);

  });

}

timeout1()

  .then(timeout2)

  .then(timeout3)

  .then(timeout4)

  .then(function() {

    console.log(Date.now() + " timout4 callback");

  });

$ node promisTest.js

1450278983342 start timeout1

1450278985343 start timeout2

1450278988351 start timeout3

1450278992356 start timeout4

1450278997370 timout4 callback

接下來(lái)我們可以再繼續(xù)研究一下onFulfilled函數(shù)傳入入?yún)⒌膯?wèn)題。

我們已經(jīng)知道,如果上一個(gè)onFulfilled函數(shù)返回了一個(gè)普通的值,那么這個(gè)值為作為這個(gè)onFulfilled函數(shù)的入?yún)ⅲ荒敲慈绻弦粋€(gè)onFulfilled返回了一個(gè)Promise變量,這個(gè)onFulfilled的入?yún)⒂謥?lái)自哪里?

答案是,這個(gè)onFulfilled函數(shù)的入?yún)ⅲ巧弦粋€(gè)Promise中調(diào)用resolve函數(shù)時(shí)傳入的值。

跳躍的有點(diǎn)大一時(shí)間無(wú)法接受對(duì)不對(duì),讓我們來(lái)好好縷一縷。

首先,Promise.resolve這個(gè)函數(shù)是什么,用MDN上面文鄒鄒的說(shuō)法

用成功值value解決一個(gè)Promise對(duì)象。如果該value為可繼續(xù)的(thenable,即帶有then方法),返回的Promise對(duì)象會(huì)“跟隨”這個(gè)value

簡(jiǎn)而言之,這就是異步調(diào)用成功情況下的回調(diào)。

我們來(lái)看看普通的異步接口中,成功情況的回調(diào)是什么樣的,就拿nodejs的上的fs.readFile(file[, options], callback)來(lái)說(shuō),它的典型調(diào)用例子如下

fs.readFile('/etc/passwd', function (err, data) {

 if (err) throw err;

 console.log(data);

});

因?yàn)閷?duì)于fs.readFile這個(gè)函數(shù)而言,無(wú)論成功還是失敗,它都會(huì)調(diào)用callback這個(gè)回調(diào)函數(shù),所以這個(gè)回調(diào)接受兩個(gè)入?yún)?,即失敗時(shí)的異常描述err和成功時(shí)的返回結(jié)果data。

那么假如我們用Promise來(lái)重構(gòu)這個(gè)讀取文件的例子,我們應(yīng)該怎么寫(xiě)呢?

首先是封裝fs.readFile函數(shù):

function readFile(fileName) {

  return new Promise(function(resolve, reject) {

    fs.readFile(fileName, function (err, data) {

      if (err) {

        reject(err);

      } else {

        resolve(data);

      }

    });

  });

}

其次是調(diào)用:

readFile('theFile.txt').then(

  function(data) {

    console.log(data);

  }, 

  function(err) {

    throw err;

  }  

);

想象一下,在其他語(yǔ)言的讀取文件的同步調(diào)用接口的里面,文件的內(nèi)容通常是放在哪里?函數(shù)返回值對(duì)不對(duì)!答案出來(lái)了,這個(gè)resolve的入?yún)⑹鞘裁矗烤褪钱惒秸{(diào)用成功情況下的返回值。

有了這個(gè)概念之后,我們就不難理解“onFulfilled函數(shù)的入?yún)?,是上一個(gè)Promise中調(diào)用resolve函數(shù)時(shí)傳入的值”這件事了。因?yàn)閛nFulfilled的任務(wù),就是對(duì)上一個(gè)異步調(diào)用成功后的結(jié)果做處理的。

哎終于理順了。。。

總結(jié)

下面請(qǐng)?jiān)试S我用一段代碼對(duì)本文講解到的要點(diǎn)進(jìn)行總結(jié):

function callp1() {

  console.log(Date.now() + " start callp1");

  return new Promise(function(res, rej) {

    setTimeout(res, 2000);

  });

}

function callp2() {

  console.log(Date.now() + " start callp2");

  return new Promise(function(res, rej) {

    setTimeout(function() {

      res({arg1: 4, arg2: "arg2 value"});

    }, 3000);

  });

}

function callp3(arg) {

  console.log(Date.now() + " start callp3 with arg = " + arg);

  return new Promise(function(res, rej) {

    setTimeout(function() {

      res("callp3");

    }, arg * 1000);

  });

}

callp1().then(function() {

  console.log(Date.now() + " callp1 return");

  return callp2();

}).then(function(ret) {

  console.log(Date.now() + " callp2 return with ret value = " + JSON.stringify(ret));

  return callp3(ret.arg1);

}).then(function(ret) {

  console.log(Date.now() + " callp3 return with ret value = " + ret);

})

$ node promisTest.js

1450191479575 start callp1

1450191481597 callp1 return

1450191481599 start callp2

1450191484605 callp2 return with ret value = {"arg1":4,"arg2":"arg2 value"}

1450191484605 start callp3 with arg = 4

1450191488610 callp3 return with ret value = callp3

以上這篇使用Promise解決多層異步調(diào)用的簡(jiǎn)單學(xué)習(xí)心得就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考

更多信息請(qǐng)查看網(wǎng)絡(luò)編程
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門公布的正式信息和咨詢?yōu)闇?zhǔn)!

2025國(guó)考·省考課程試聽(tīng)報(bào)名

  • 報(bào)班類型
  • 姓名
  • 手機(jī)號(hào)
  • 驗(yàn)證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡(jiǎn)要咨詢 | 簡(jiǎn)要咨詢須知 | 新媒體/短視頻平臺(tái) | 手機(jī)站點(diǎn) | 投訴建議
工業(yè)和信息化部備案號(hào):滇ICP備2023014141號(hào)-1 云南省教育廳備案號(hào):云教ICP備0901021 滇公網(wǎng)安備53010202001879號(hào) 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號(hào)
云南網(wǎng)警備案專用圖標(biāo)
聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號(hào):hfpxwx
咨詢QQ:1093837350(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報(bào)警專用圖標(biāo)