JavaScript之promise_動力節(jié)點(diǎn)Java學(xué)院整理
在JavaScript的世界中,所有代碼都是單線程執(zhí)行的。
由于這個“缺陷”,導(dǎo)致JavaScript的所有網(wǎng)絡(luò)操作,瀏覽器事件,都必須是異步執(zhí)行。異步執(zhí)行可以用回調(diào)函數(shù)實(shí)現(xiàn):
function callback() { console.log('Done'); } console.log('before setTimeout()'); setTimeout(callback, 1000); // 1秒鐘后調(diào)用callback函數(shù) console.log('after setTimeout()');
觀察上述代碼執(zhí)行,在Chrome的控制臺輸出可以看到:
before setTimeout()
after setTimeout()
(等待1秒后)
Done
可見,異步操作會在將來的某個時間點(diǎn)觸發(fā)一個函數(shù)調(diào)用。
AJAX就是典型的異步操作。以上一節(jié)的代碼為例:
request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status === 200) { return success(request.responseText); } else { return fail(request.status); } } }
把回調(diào)函數(shù)success(request.responseText)和fail(request.status)寫到一個AJAX操作里很正常,但是不好看,而且不利于代碼復(fù)用。
有沒有更好的寫法?比如寫成這樣:
var ajax = ajaxGet('http://...'); ajax.ifSuccess(success) .ifFail(fail);
這種鏈?zhǔn)綄懛ǖ暮锰幵谟?,先統(tǒng)一執(zhí)行AJAX邏輯,不關(guān)心如何處理結(jié)果,然后,根據(jù)結(jié)果是成功還是失敗,在將來的某個時候調(diào)用success函數(shù)或fail函數(shù)。
古人云:“君子一諾千金”,這種“承諾將來會執(zhí)行”的對象在JavaScript中稱為Promise對象。
Promise有各種開源實(shí)現(xiàn),在ES6中被統(tǒng)一規(guī)范,由瀏覽器直接支持。先測試一下你的瀏覽器是否支持Promise:
'use strict'; new Promise(function () {}); // 直接運(yùn)行測試: alert('支持Promise!');
我們先看一個最簡單的Promise例子:生成一個0-2之間的隨機(jī)數(shù),如果小于1,則等待一段時間后返回成功,否則返回失?。?br />
function test(resolve, reject) { var timeOut = Math.random() * 2; log('set timeout to: ' + timeOut + ' seconds.'); setTimeout(function () { if (timeOut < 1) { log('call resolve()...'); resolve('200 OK'); } else { log('call reject()...'); reject('timeout in ' + timeOut + ' seconds.'); } }, timeOut * 1000); }
這個test()函數(shù)有兩個參數(shù),這兩個參數(shù)都是函數(shù),如果執(zhí)行成功,我們將調(diào)用resolve('200 OK'),如果執(zhí)行失敗,我們將調(diào)用reject('timeout in ' + timeOut + ' seconds.')??梢钥闯觯瑃est()函數(shù)只關(guān)心自身的邏輯,并不關(guān)心具體的resolve和reject將如何處理結(jié)果。
有了執(zhí)行函數(shù),我們就可以用一個Promise對象來執(zhí)行它,并在將來某個時刻獲得成功或失敗的結(jié)果:
var p1 = new Promise(test); var p2 = p1.then(function (result) { console.log('成功:' + result); }); var p3 = p2.catch(function (reason) { console.log('失?。? + reason); });
變量p1是一個Promise對象,它負(fù)責(zé)執(zhí)行test函數(shù)。由于test函數(shù)在內(nèi)部是異步執(zhí)行的,當(dāng)test函數(shù)執(zhí)行成功時,我們告訴Promise對象:
// 如果成功,執(zhí)行這個函數(shù): p1.then(function (result) { console.log('成功:' + result); });
當(dāng)test函數(shù)執(zhí)行失敗時,我們告訴Promise對象:
p2.catch(function (reason) { console.log('失敗:' + reason); });
Promise對象可以串聯(lián)起來,所以上述代碼可以簡化為:
new Promise(test).then(function (result) { console.log('成功:' + result); }).catch(function (reason) { console.log('失?。? + reason); });
實(shí)際測試一下,看看Promise是如何異步執(zhí)行的:
'use strict'; // 清除log: var logging = document.getElementById('test-promise-log'); while (logging.children.length > 1) { logging.removeChild(logging.children[logging.children.length - 1]); } // 輸出log到頁面: function log(s) { var p = document.createElement('p'); p.innerHTML = s; logging.appendChild(p); } new Promise(function (resolve, reject) { log('start new Promise...'); var timeOut = Math.random() * 2; log('set timeout to: ' + timeOut + ' seconds.'); setTimeout(function () { if (timeOut < 1) { log('call resolve()...'); resolve('200 OK'); } else { log('call reject()...'); reject('timeout in ' + timeOut + ' seconds.'); } }, timeOut * 1000); }).then(function (r) { log('Done: ' + r); }).catch(function (reason) { log('Failed: ' + reason); });
Log:
start new Promise...
set timeout to: 0.5354042750614991 seconds.
call resolve()...
Done: 200 OK
可見Promise最大的好處是在異步執(zhí)行的流程中,把執(zhí)行代碼和處理結(jié)果的代碼清晰地分離了:
Promise還可以做更多的事情,比如,有若干個異步任務(wù),需要先做任務(wù)1,如果成功后再做任務(wù)2,任何任務(wù)失敗則不再繼續(xù)并執(zhí)行錯誤處理函數(shù)。
要串行執(zhí)行這樣的異步任務(wù),不用Promise需要寫一層一層的嵌套代碼。有了Promise,我們只需要簡單地寫:
job1.then(job2).then(job3).catch(handleError);
其中,job1、job2和job3都是Promise對象。
下面的例子演示了如何串行執(zhí)行一系列需要異步計(jì)算獲得結(jié)果的任務(wù):
'use strict'; var logging = document.getElementById('test-promise2-log'); while (logging.children.length > 1) { logging.removeChild(logging.children[logging.children.length - 1]); } function log(s) { var p = document.createElement('p'); p.innerHTML = s; logging.appendChild(p); } // 0.5秒后返回input*input的計(jì)算結(jié)果: function multiply(input) { return new Promise(function (resolve, reject) { log('calculating ' + input + ' x ' + input + '...'); setTimeout(resolve, 500, input * input); }); } // 0.5秒后返回input+input的計(jì)算結(jié)果: function add(input) { return new Promise(function (resolve, reject) { log('calculating ' + input + ' + ' + input + '...'); setTimeout(resolve, 500, input + input); }); } var p = new Promise(function (resolve, reject) { log('start new Promise...'); resolve(123); }); p.then(multiply) .then(add) .then(multiply) .then(add) .then(function (result) { log('Got value: ' + result); });
Log:
start new Promise...
calculating 123 x 123...
calculating 15129 + 15129...
calculating 30258 x 30258...
calculating 915546564 + 915546564...
Got value: 1831093128
setTimeout可以看成一個模擬網(wǎng)絡(luò)等異步執(zhí)行的函數(shù)?,F(xiàn)在,我們把上一節(jié)的AJAX異步執(zhí)行函數(shù)轉(zhuǎn)換為Promise對象,看看用Promise如何簡化異步處理:
'use strict'; // ajax函數(shù)將返回Promise對象: function ajax(method, url, data) { var request = new XMLHttpRequest(); return new Promise(function (resolve, reject) { request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status === 200) { resolve(request.responseText); } else { reject(request.status); } } }; request.open(method, url); request.send(data); }); } var log = document.getElementById('test-promise-ajax-result'); var p = ajax('GET', '/api/categories'); p.then(function (text) { // 如果AJAX成功,獲得響應(yīng)內(nèi)容 log.innerText = text; }).catch(function (status) { // 如果AJAX失敗,獲得響應(yīng)代碼 log.innerText = 'ERROR: ' + status; });
除了串行執(zhí)行若干異步任務(wù)外,Promise還可以并行執(zhí)行異步任務(wù)。
試想一個頁面聊天系統(tǒng),我們需要從兩個不同的URL分別獲得用戶的個人信息和好友列表,這兩個任務(wù)是可以并行執(zhí)行的,用Promise.all()實(shí)現(xiàn)如下:
var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); // 同時執(zhí)行p1和p2,并在它們都完成后執(zhí)行then: Promise.all([p1, p2]).then(function (results) { console.log(results); // 獲得一個Array: ['P1', 'P2'] });
有些時候,多個異步任務(wù)是為了容錯。比如,同時向兩個URL讀取用戶的個人信息,只需要獲得先返回的結(jié)果即可。這種情況下,用Promise.race()實(shí)現(xiàn):
var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); Promise.race([p1, p2]).then(function (result) { console.log(result); // 'P1' });
由于p1執(zhí)行較快,Promise的then()將獲得結(jié)果'P1'。p2仍在繼續(xù)執(zhí)行,但執(zhí)行結(jié)果將被丟棄。
如果我們組合使用Promise,就可以把很多異步任務(wù)以并行和串行的方式組合起來執(zhí)行。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
js驗(yàn)證手機(jī)號、密碼、短信驗(yàn)證碼代碼工具類
這篇文章主要介紹了js驗(yàn)證手機(jī)號、密碼、短信驗(yàn)證碼代碼工具類,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-01-01JavaScript中forEach和map詳細(xì)講解
foreach和map都是JavaScript中數(shù)組的常用方法,它們都可以對數(shù)組中的每個元素執(zhí)行一個函數(shù),但是它們有一些區(qū)別,下面這篇文章主要給大家介紹了關(guān)于JavaScript中forEach和map詳細(xì)講解的相關(guān)資料,需要的朋友可以參考下2023-11-11JS中的算法與數(shù)據(jù)結(jié)構(gòu)之常見排序(Sort)算法詳解
這篇文章主要介紹了JS中的算法與數(shù)據(jù)結(jié)構(gòu)之常見排序(Sort)算法,結(jié)合實(shí)例形式詳細(xì)分析了js常見排序算法中的冒泡排序、選擇排序、插入排序、希爾排序、歸并排序、快速排序等算法相關(guān)實(shí)現(xiàn)技巧與操作注意事項(xiàng),需要的朋友可以參考下2019-08-08JavaScript實(shí)現(xiàn)的聯(lián)動菜單特效示例
這篇文章主要介紹了JavaScript實(shí)現(xiàn)的聯(lián)動菜單特效,涉及javascript事件響應(yīng)及頁面元素屬性動態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-07-07Bootstrap FileInput實(shí)現(xiàn)圖片上傳功能
這篇文章主要為大家詳細(xì)介紹了Bootstrap FileInput實(shí)現(xiàn)圖片上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-01-01微信小程序如何修改本地緩存key中單個數(shù)據(jù)的詳解
這篇文章主要介紹了微信小程序如何修改本地緩存key中單個數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04使用Github?Actions發(fā)布npm包完整過程詳解
本文包含本地發(fā)布npm包發(fā)布流程,?和?github?action自動發(fā)布npm包流程,幫助你更好的發(fā)布自己或公司的npm包,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09