JavaScript中Promise的簡單使用及其原理詳解
Promise是ES6最重要的特性之一,今天來系統(tǒng)且細(xì)致的研究一下Promise的用法以及原理。
按照我往常的理解,Promise是一個構(gòu)造函數(shù),有all、resolve、reject、then、catch等幾個方法,一般情況下,在涉及到異步操作時才會用到Promise。
所以我接下來先new一個Promise對象,并在其中進(jìn)行一些異步操作:
// 使用Promise的時候一般會把它包裹在一個函數(shù)中,并在函數(shù)的最后返回這個Promise對象 function runPro()( var a = new Promise((resolve, reject) => { setTimeout(() => { console.log('work done!'); resolve('success'); }, 1000); }); return a; ) runPro()
在上面的代碼中,Promise的構(gòu)造函數(shù)接收一個箭頭函數(shù)作為參數(shù),這個箭頭函數(shù)又有兩個參數(shù),分別是resolve和reject,我在這個箭頭函數(shù)中使用setTimeout進(jìn)行了一些異步操作,異步操作中執(zhí)行了resolve方法,并給resolve方法傳了一個字符串‘success’作為參數(shù)。
執(zhí)行這段代碼會發(fā)現(xiàn),等待了1秒鐘后(因?yàn)槲以趕etTimeout中設(shè)置的等待時間是1000毫秒),輸出了‘work done!’。
這時候并沒有發(fā)現(xiàn)Promise有什么特別的作用,而且resolve和reject這兩個的作用也并沒有體現(xiàn)出來。
之前我們說過Promise這個構(gòu)造函數(shù)上有then、catch方法,在上面的代碼片段中,runPro函數(shù)最后return了一個Promise對象,所以我們可以在runPro函數(shù)執(zhí)行完成之后使用then對Promise對象進(jìn)行進(jìn)一步的操作:
runPro().then((res) => { console.log('then:', res); //TODO something });
輸出結(jié)果:
在runPro返回的Promise對象上直接調(diào)用then方法,then方法接收一個函數(shù)作為參數(shù)A,并且這個箭頭函數(shù)也會接收一個參數(shù)B,這個參數(shù)B的值就是前面代碼中resolve方法所傳遞的字符串‘success’。
執(zhí)行代碼,會在1秒后首先輸出‘work done!’,緊接著輸出‘then: success’。
這個時候,就可以簡單的體現(xiàn)出來Promise的作用了,在前面的代碼中,then方法就像是Promise的回調(diào)函數(shù),當(dāng)Promise中的異步操作執(zhí)行完之后,通過鏈?zhǔn)秸{(diào)用的方式執(zhí)行回調(diào)函數(shù)。
這里的關(guān)鍵點(diǎn)就在于鏈?zhǔn)秸{(diào)用上,當(dāng)實(shí)際使用中遇見多層回調(diào)的情況時,Promise的強(qiáng)大之處才能夠體現(xiàn)出來:
function runPro2(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro2'); }, 1000); }); return a; }; function runPro3(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro3'); }, 1000); }); return a; }; runPro().then(() => { return runPro2(); }).then(() => { return runPro3(); }).then(() => { console.log('all done') })
到這里為止,大概已經(jīng)明白了,Promise是一個在異步操作過程中,等待其中異步操作完成之后執(zhí)行其回調(diào)函數(shù)的一種結(jié)構(gòu)體。但是其中的原理還是模糊不清,其中resolve和reject這兩個參數(shù)還沒有搞清楚,只知道在前面的幾個代碼片段中都調(diào)用了resolve函數(shù),resolve是做什么的并沒有體現(xiàn)出來。
關(guān)于resolve和reject,我以前的理解是Promise中的異步操作執(zhí)行成功后調(diào)用resolve函數(shù),異步操作執(zhí)行失敗后調(diào)用reject函數(shù),后來發(fā)現(xiàn)這種理解其實(shí)是不準(zhǔn)確的。
在理解這兩個函數(shù)的正確作用之前,我們首先要知道Promise一個重要的特性:狀態(tài)
Promise的狀態(tài):
一個Promise對象的當(dāng)前狀態(tài)必須為以下三種狀態(tài)中的一種:等待(Pending)、完成(Fulfilled)、拒絕(Rejected)。
Pending:
異步操作完成之前,Promise處于等待狀態(tài),這時候的Promise可以遷移至Fulfilled或者Rejected。
Fulfilled:
異步操作完成之后,Promise可能從Pending狀態(tài)遷移至Fulfilled狀態(tài),F(xiàn)ulfilled狀態(tài)的Promise必須擁有一個不可變的終值,并且Fulfilled狀態(tài)的Promise不能遷移為其他狀態(tài)。
Rejected:
異步操作完成之后,Promise可能從Pending狀態(tài)遷移至Rejected狀態(tài),Rejected狀態(tài)的Promise必須擁有一個不可變的拒絕原因,并且Rejected狀態(tài)的Promise不能遷移為其他狀態(tài)。
了解了Promise的三種狀態(tài)之后,我們再來說說resolve和reject這兩個函數(shù)的作用:
- resolve函數(shù)將Promise設(shè)置為Fulfilled狀態(tài),reject函數(shù)將Promise設(shè)置為Rejected狀態(tài)。
- 設(shè)置為Fulfilled或者rejected狀態(tài)后,即表示Promise中的異步操作執(zhí)行完成,這時程序就會執(zhí)行then回調(diào)函數(shù)。
- resolve和reject函數(shù)傳遞的參數(shù),將由then函數(shù)中的箭頭函數(shù)接收。
實(shí)際上,理解Promise的關(guān)鍵點(diǎn)就在于這個狀態(tài),通過維護(hù)狀態(tài)、傳遞狀態(tài)的方法來進(jìn)行及時的回調(diào)。
所以,如下面代碼所示,當(dāng)使用Promise進(jìn)行異步操作的時候,其中有幾個關(guān)鍵點(diǎn)需要特別注意:
在一個函數(shù)中new了一個Promise對象之后,函數(shù)的最后必須把這個Promise對象return出來,否則這個函數(shù)就無法使用then函數(shù)進(jìn)行回調(diào);
異步操作中必須執(zhí)行resolve或者reject函數(shù),否則這個Promise一直處于Pending狀態(tài),代碼就不會執(zhí)行它的回調(diào)函數(shù)。
function runPro(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('resolve'); console.log('this is runPro'); }, 1000); }); return a; } runPro().then((res) => { console.log(res); })
同樣的道理,當(dāng)異步操作執(zhí)行失敗時,代碼通過執(zhí)行reject函數(shù)的方式,將Promise的狀態(tài)設(shè)置為Rejected,并返回一個拒絕原因:
function runPro(item){ var a = new Promise((resolve, reject) => { setTimeout(() => { if(item >= 18) { console.log('item 大于 18'); resolve('一切正常!'); }else { console.log('item 小于 18'); reject('18+電影不允許放映!'); } }, 1000); }); return a; } runPro(13) .then((res) => { console.log('resolve:',res); },(rej) => { console.log('reject:', rej); })
我們給runPro函數(shù)傳遞不同的參數(shù),runPro接受參數(shù)后進(jìn)行一個異步的判斷,如果這個參數(shù)的值小于18,執(zhí)行reject函數(shù),反之則執(zhí)行resolve函數(shù),異步操作完成之后,執(zhí)行then回調(diào)函數(shù),這里的回調(diào)函數(shù)可以接收兩個箭頭函數(shù)作為參數(shù),分別對應(yīng)了resolve函數(shù)的回調(diào)和reject函數(shù)的回調(diào),這兩個箭頭函數(shù)可以分別拿到resolve和reject傳遞的參數(shù)。
如下面截圖所示,分別給runPro傳遞兩個不同值后,得到了兩種不同的結(jié)果:
catch函數(shù)的用法
在Promise中,catch函數(shù)可以替代reject函數(shù)使用,用來指定接收reject的回調(diào):
function runPro(item){ var a = new Promise((resolve, reject) => { setTimeout(() => { if(item >= 18) { console.log('item 大于 18'); resolve('一切正常!'); }else { console.log('item 小于 18'); reject('18+電影不允許放映!'); } }, 1000); }); return a; } runPro(13) .then((res) => { console.log('resolve:',res); }) .catch((rej) => { console.log('catch:', rej); })
如上面代碼所示,對調(diào)函數(shù)then只有一個箭頭函數(shù)作為參數(shù),這種情況下,這個箭頭函數(shù)就被指定用來接收resolve函數(shù)的回調(diào),而reject函數(shù)的回調(diào)則被catch函數(shù)來接收:
這個地方使用catch函數(shù)來接收reject的回調(diào)有一個優(yōu)點(diǎn),當(dāng)前面的then回調(diào)函數(shù)中出現(xiàn)位置錯誤時,catch函數(shù)可以對錯誤信息進(jìn)行處理,而不會導(dǎo)致代碼報(bào)錯。這個原理和常用的try/catch語句相同。
function runPro(item){ var a = new Promise((resolve, reject) => { setTimeout(() => { if(item >= 18) { console.log('item 大于 18'); resolve('一切正常!'); }else { console.log('item 小于 18'); reject('18+電影不允許放映!'); } }, 1000); }); return a; } runPro(19) .then((res) => { console.log(adc); // 這里的adc是一個未定義的變量,當(dāng)代碼執(zhí)行到這里時,會拋出Error信息導(dǎo)致代碼卡死 console.log('resolve:',res); }, (rej) => { console.log('reject:',rej); }); runPro(19) .then((res) => { console.log(abc); // 這里的abc是一個未定義的變量,但是由于后邊使用.catch函數(shù)進(jìn)行了異常捕獲,所以程序不會報(bào)錯。而且錯誤原因也會作為參數(shù)傳遞到后面.catch函數(shù)的參數(shù)中 console.log('resolve:',res); }) .catch((rej) => { console.log('catch:', rej); })
all函數(shù) / race函數(shù)并行異步操作
Promise的all函數(shù)和race函數(shù)都提供了并行異步操作的能力,二者的區(qū)別在于,當(dāng)這些并行的異步操作耗時不同時,all函數(shù)是在所有的異步操作都執(zhí)行完之后才會執(zhí)行,而race函數(shù)則會在第一個異步操作完成之后立即執(zhí)行。
function runPro1(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro1'); }, 1000); }); return a; } function runPro2(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro2'); }, 2000); }); return a; }; function runPro3(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro3'); }, 3000); }); return a; }; Promise.all([runPro1(), runPro2(), runPro3()]) .then((res) => { console.log('all:', res); })
function runPro1(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro1'); }, 1000); }); return a; } function runPro2(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro2'); }, 2000); }); return a; }; function runPro3(){ var a = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); console.log('this is runPro3'); }, 3000); }); return a; }; Promise.races([runPro1(), runPro2(), runPro3()]) .then((res) => { console.log('all:', res); })
如上兩個代碼片段所示,all函數(shù)和race都接收一個數(shù)組作為參數(shù),這個數(shù)組中的值就是我們要進(jìn)行并行執(zhí)行的異步操作。這里我們同樣使用then函數(shù)作為異步操作完成的回調(diào)函數(shù)。
同時我們通過console輸出發(fā)現(xiàn),在race函數(shù)的回調(diào)函數(shù)開始執(zhí)行的時候,另外兩個沒有執(zhí)行完成的異步操作并沒有停止,依舊在執(zhí)行。
到此這篇關(guān)于JavaScript中Promise的簡單使用及其原理詳解的文章就介紹到這了,更多相關(guān)JavaScript Promise內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)上下浮動的窗口效果代碼
這篇文章主要介紹了JavaScript實(shí)現(xiàn)上下浮動的窗口效果代碼,可實(shí)現(xiàn)自定義窗口在垂直方向上彈性移動的效果,代碼備有完整的注釋說明供讀者參考學(xué)習(xí),需要的朋友可以參考下2015-10-10JavaScript網(wǎng)絡(luò)請求工具庫axios使用實(shí)例探索
這篇文章主要為大家介紹了JavaScript網(wǎng)絡(luò)請求工具庫axios使用實(shí)例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01javascript算法題:求任意一個1-9位不重復(fù)的N位數(shù)在該組合中的大小排列序號
這篇文章主要介紹了javascript算法題:求任意一個1-9位不重復(fù)的N位數(shù)在該組合中的大小排列序號,需要的朋友可以參考下2015-04-04