轉自:廖雪峯的官方網站javascript
在JavaScript的世界中,全部代碼都是單線程執行的。java
因爲這個「缺陷」,致使JavaScript的全部網絡操做,瀏覽器事件,都必須是異步執行。異步執行能夠用回調函數實現:ajax
function callback() { console.log('Done'); } console.log('before setTimeout()'); setTimeout(callback, 1000); // 1秒鐘後調用callback函數 console.log('after setTimeout()');
觀察上述代碼執行,在Chrome的控制檯輸出能夠看到:promise
before setTimeout() after setTimeout() (等待1秒後) Done
可見,異步操做會在未來的某個時間點觸發一個函數調用。瀏覽器
AJAX就是典型的異步操做。以上一節的代碼爲例:ruby
request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status === 200) { return success(request.responseText); } else { return fail(request.status); } } }
把回調函數success(request.responseText)
和fail(request.status)
寫到一個AJAX操做裏很正常,可是很差看,並且不利於代碼複用。網絡
有沒有更好的寫法?好比寫成這樣:app
var ajax = ajaxGet('http://...'); ajax.ifSuccess(success) .ifFail(fail);
這種鏈式寫法的好處在於,先統一執行AJAX邏輯,不關心如何處理結果,而後,根據結果是成功仍是失敗,在未來的某個時候調用success
函數或fail
函數。dom
古人云:「君子一言既出;駟馬難追」,這種「承諾未來會執行」的對象在JavaScript中稱爲Promise對象。異步
Promise有各類開源實現,在ES6中被統一規範,由瀏覽器直接支持。先測試一下你的瀏覽器是否支持Promi'use strict';
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()
函數有兩個參數,這兩個參數都是函數,若是執行成功,咱們將調用resolve('200 OK')
,若是執行失敗,咱們將調用reject('timeout in ' + timeOut + ' seconds.')
。能夠看出,test()
函數只關心自身的邏輯,並不關心具體的resolve
和reject
將如何處理結果。
有了執行函數,咱們就能夠用一個Promise對象來執行它,並在未來某個時刻得到成功或失敗的結果:
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對象,它負責執行test
函數。因爲test
函數在內部是異步執行的,當test
函數執行成功時,咱們告訴Promise對象:
// 若是成功,執行這個函數: p1.then(function (result) { console.log('成功:' + result); });
當test
函數執行失敗時,咱們告訴Promise對象:
p2.catch(function (reason) { console.log('失敗:' + reason); });
Promise對象能夠串聯起來,因此上述代碼能夠簡化爲:
new Promise(test).then(function (result) { console.log('成功:' + result); }).catch(function (reason) { console.log('失敗:' + reason); });
實際測試一下,看看Promise是如何異步執行的'use strict';
可見Promise最大的好處是在異步執行的流程中,把執行代碼和處理結果的代碼清晰地分離了:
Promise還能夠作更多的事情,好比,有若干個異步任務,須要先作任務1,若是成功後再作任務2,任何任務失敗則再也不繼續並執行錯誤處理函數。
要串行執行這樣的異步任務,不用Promise須要寫一層一層的嵌套代碼。有了Promise,咱們只須要簡單地寫:
job1.then(job2).then(job3).catch(handleError);
其中,job1
、job2
和job3
都是Promise對象。
下面的例子演示瞭如何串行執行一系列須要異步計算得到結果的任務:'use strict';
setTimeout
能夠當作一個模擬網絡等異步執行的函數。如今,咱們把上一節的AJAX異步執行函數轉換爲Promise對象,看看用Promise如何簡化異步處理:
除了串行執行若干異步任務外,Promise還能夠並行執行異步任務。
試想一個頁面聊天系統,咱們須要從兩個不一樣的URL分別得到用戶的我的信息和好友列表,這兩個任務是能夠並行執行的,用Promise.all()
實現以下:
var p1 = new Promise(function (resolve, reject) { setTimeout(resolve, 500, 'P1'); }); var p2 = new Promise(function (resolve, reject) { setTimeout(resolve, 600, 'P2'); }); // 同時執行p1和p2,並在它們都完成後執行then: Promise.all([p1, p2]).then(function (results) { console.log(results); // 得到一個Array: ['P1', 'P2'] });
有些時候,多個異步任務是爲了容錯。好比,同時向兩個URL讀取用戶的我的信息,只須要得到先返回的結果便可。這種狀況下,用Promise.race()
實現:
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
執行較快,Promise的then()
將得到結果'P1'
。p2
仍在繼續執行,但執行結果將被丟棄。
若是咱們組合使用Promise,就能夠把不少異步任務以並行和串行的方式組合起來執行。