Promise 對象用來傳遞異步操做消息,表明一個將來纔會知道結果的事件,而且對不一樣事件提供統一的 API 以便進一步處理。Promise 具備如下特色:ajax
對象狀態不受外界影響:Promise 表明的異步操做有三個狀態:json
一旦狀態改變,就不會再變:Promise 的狀態只有2種可能:數組
對於同一個 promise, 當以上狀態發生一個(只能發生其一),就不會再改變了。以後任什麼時候間你都能獲得這個狀態,且永不改變。
有了 Promise 就能夠將層層的回調寫爲同步的樣子,表示起來更清晰。不過須要注意如下幾點:promise
Promise 的基本結構以下:瀏覽器
var promise = new Promise(function(resolve, reject){ if(/*異步操做成功*/){ resolve(value); } else { reject(error); } });
構造函數接受一個回調函數爲參數,回調函數具備2個參數,也都是函數,resolve 在 Promise 狀態變爲 resolved 時調用,reject 在 Promise 狀態變爲 rejected 時調用。resolve 的接受一個參數——值或另外一個 promise 對象; rejectj接受一個參數——錯誤。須要說明的是,這裏的 resole 和 reject 函數已經由系統部署好了,咱們能夠不寫。服務器
promise 構建好之後咱們就能夠調用它的then()
方法,then(resolve(value){},reject(value){})
方法接受2個函數參數,resolve 在 Promise 狀態變爲 resolved 時調用,reject 在 Promise 狀態變爲 rejected 時調用。其中 reject 參數是可選的。和構造函數不一樣的是,then 方法的 reject 和 resolve 都使用 promise 傳出的值做爲其惟一的參數。app
這裏寫一個簡單的例子,理解一下:異步
function timeout(ms){ return new Promise((resolve, reject) => { console.log("promise"); //"promise" setTimeout(resolve, ms, 'done'); }); } timeout(2000).then((value) => { console.log(value); //2秒後獲得 "done" });
利用 Promise 異步加載圖片:async
function loadImageAsync(url){ return new Promise(function(resole, reject){ var image = new Image(); image.onload = function(){ resolve(image); }; image.onerror = function(){ reject(new Error(`Could not load image at ${url}`)); }; image.src = url; }); }
利用 Promise 實現 Ajax:函數
var id = document.getElementById("primary"); var getJSON = function(url){ var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.response = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler(){ if(client.readyState !== 4) return; if(this.status === 200){ resolve(client.response); } else { reject(new Error(this.statusText)); } } }); return promise; } getJSON('info.json').then( json => id.innerHTML = "<pre>" + json + "</pre>", err => id.innerHTML = err );
若是 resolve 的參數是一個promise:
var p1 = new Promise(function(resolve, reject){ //... }); var p2 = new Promise(function(resolve, reject){ //... resolve(p1); });
上面代碼中 p1 的狀態傳給了 p2,也就是p1運行完成(狀態爲 resolve 或 reject)後 p2 的回調函數會馬上開始執行:
var p1 = new Promise(function(resolve, reject){ setTimeout(() => reject(new Error('failed')), 3000); }); var p2 = new Promise(function(resolve, reject){ setTimeout(() => resolve(p1), 1000); }); p2.then(result => console.log(result)); p2.catch(error => console.log(error));
p1 創建,進入 setTimeout 異步計時器。以後 p2 創建,進入 setTimeout 異步計時器。1s 後 p2 準備執行 resolve, 可是 resolve 的參數是 p1, 此時 p1 仍是 Pending 狀態,因此 p2 開始等待。又過了 2s, p1 的 reject 執行,變爲 rejected 狀態,隨即 p2 也跟着變成 rejected 狀態。
then(resolve(value){},reject(value){})
方法接受2個函數參數,resolve 在 Promise 狀態變爲 resolved 時調用,reject 在 Promise 狀態變爲 rejected 時調用。其中 reject 參數是可選的。和構造函數不一樣的是,then 方法的 reject 和 resolve 都使用 promise 傳出的值做爲其惟一的參數。then()
方法返回一個新的 Promise 實例,注意,不是以前那個。所以能夠用鏈式調用,不斷添加"回調"函數。 then 的返回值成了下一個 then 中回調函數的參數:
var p = new Promise(function(resolve, reject){ resolve("from new Promise"); }).then(function (value){ console.log(value); //from new Promise 其次輸出這個 return "from the first 'then'"; }).then(function(value){ console.log(value); //from the first 'then' 最後輸出這個 return "from the second 'then'"; }); console.log(p); //Promise{...} 先輸出這個
注意,若是 promise 的狀態是 resolved 則執行 then參數中的第一個回調函數;若是 promise 的狀態是 rejected 則執行 then參數中的第二個回調函數。這個狀態是不斷傳遞下來的,這一點和以前的例子相似。
catch(reject) 方法是 then(null, reject)
的別名,在發生錯誤的時候執行其參數函數:
new Promise(function(resolve, reject){ resolve("resolved"); }).then(function(val){ console.log(val); //resolved throw new Error("man-made Error"); }).catch(function(err){ console.log(err.message); //man-made Error });
錯誤會從最初的請求沿着回調函數,一直被傳遞下來。這一點和傳統的錯誤冒泡相似,不管哪裏有錯誤均可以被捕獲到:
new Promise(function(resolve, reject){ reject(new Error("original Error")); }).then(function(val){ console.log(val); //不執行 throw new Error("man-made Error"); }).catch(function(err){ console.log(err.message); //original Error });
固然也能夠在半路截住錯誤:
new Promise(function(resolve, reject){ reject(new Error("original Error")); }).then(function(val){ console.log(val); //不執行 throw new Error("man-made Error"); }, function(err){ console.log(`Uncaught Error: ${err.message}`); //Uncaught Error: original Error }).catch(function(err){ console.log(err.message); //不執行 });
這裏須要注意如下幾點:
這裏須要說明的是第4條:錯誤不會到 Promise 外面是 ES6 規範的說法。具體理解(瀏覽器環境):控制檯依舊會報錯,可是不影響 promise 語句以後續代碼執行。此外,promise 語句內的異步語句(如事件,定時器等等)拋出的錯誤,不屬於 promise 內部,發生錯誤會傳播出去:
var p = new Promise(function(resolve, reject){ resolve("ok"); setTimeout(function(){throw new Error("setTimeout error")},0); }); p.then(function(val){console.log(val);}); //ok //Uncaught Error: setTimeout error
其次,就以上前兩個注意事項舉一例說明:
new Promise(function(resolve, reject){ resolve("resolved"); throw "original Error"; //被忽略 }).then(function(val){ console.log(val); //resolved throw (new Error("man-made Error")); }).catch(function(err){ console.log(err.message); //man-made Error });
catch 方法的返回值仍是一個新的 promise 對象,能夠繼續調用 then 等其餘方法:
new Promise(function(resolve, reject){ reject(new Error("reject")); }).catch(function(err){ console.log("1st catch"); //被跳過 return "continue"; }).then(function(val){ console.log(val); //continue });
若是 catch以前沒有錯誤,該 catch 會被跳過。這意味着,catch 不能捕獲在其後面的語句中出現的錯誤:
new Promise(function(resolve, reject){ resolve("resolved"); }).catch(function(err){ console.log("1st catch"); //被跳過 }).then(function(val){ console.log(val); //resolved throw (new Error()); }).catch(function(err){ console.log("2nd catch"); //2nd catch });
finally() 接受一個回調函數(無參數)爲參數,和 try...catch...finally 中的 finally 相似,不論 promise 是什麼狀態,該回調函數都必定會運行。能夠用它關閉文件,或者關閉服務器等:
server.listen(0).then(function(){ //do sth. }).finally(server.stop);
finally() 內部實現以下:
Promise.prototype.finally = function(callback){ return this.then( value => {Promise.resolve(callback()).then(() => value)}, error => {Promise.resolve(callback()).then(() => {throw error})} ); };
done() 方法用在 promise 處理語句的末端,用來處理可能未捕獲的錯誤,並拋向全局。若是其帶有參數,能夠等效爲 done() 以前多了一個 then():
p.done(fun1, fun2); //至關於 p.then(fun1,fun2).done();
done() 內部實現以下:
Promise.prototype.done = function(onResolve, onRejected){ this.then(onResolve, onRejected).catch(function(err){ setTimeout(() => {throw err}, 0); }); };
將多個 promise 對象合併成一個新的 promise 實例。其接受一個裝僅有 promise 對象的可遍歷結構爲參數,若是不是 promise 對象,系統會調用 Promise.resolve()
進行類型轉換。
promise.all() 方法獲得的新的 promise 對象狀態由構成它的全部 promise 對象決定,具體分爲2種狀況:
//僞代碼, 因爲沒有正確的 url var getJSON = function(url){ var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.response = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler(){ if(client.readyState !== 4) return; if(this.status === 200){ resolve(client.response); } else { reject(new Error(this.statusText)); } } }); return promise; } var pros = ['url1', 'url2', 'url3'].map(url => getJSON(url)); Promise.all(pros).then(function(){ console.log("all successful"); }, function(){ console.log("one rejected"); //one rejected, 因爲沒有正確的 url });
將多個 promise 對象合併成一個新的 promise 實例。其接受一個裝僅有 promise 對象的可遍歷結構爲參數,若是不是 promise 對象,系統會調用 Promise.resolve()
進行類型轉換。
和 promise.all() 不一樣的是 Promise.race() 方法獲得的新的 promise 對象狀態由構成它的 promise 對象中最早改變狀態的那一個決定。
//僞代碼, 因爲沒有正確的 url var getJSON = function(url){ var promise = new Promise(function(resolve, reject){ var client = new XMLHttpRequest(); client.open("GET", url); client.onreadystatechange = handler; client.response = "json"; client.setRequestHeader("Accept", "application/json"); client.send(); function handler(){ if(client.readyState !== 4) return; if(this.status === 200){ resolve(client.response); } else { reject(new Error(this.statusText)); } } }); return promise; } //若是5s不能得到數據就報錯 var p = Promise.race([ getJSON("url"), new Promise(function(resolve, reject){ setTimeout(() => reject(new Error("Timeout")), 5000); }) ]).then(res => console.log(res)) .catch(err => console.log(err)); //Error, 因爲沒有正確的 url
將現有對象轉化爲 promise 對象:
var p = Promise.resolve($.ajax('url')); //jQuery的 $.ajax 方法 //等同於: var p = new Promise(function(resolve){ resolve($.ajax('url')); });
若是傳入 Promise.resolve() 的對象不具備 then 方法(ie. unthenable), 則返回一個狀態爲 resolved 的新 promise 對象。
Promise.resolve("hello").then(function(val){ console.log(val); //hello });
若是你僅僅想獲得一個 promise 對象,那利用 resolve() 方法是最簡單的:
var promise = Promise.resolve();
Promise.reject(reason)
, 返回一個狀態爲 rejected 的 promise 實例。參數 reason 會被傳遞被實例的回調函數。
Promise.reject(new Error("error occured")).catch(err => console.log(err.message)); //error occured
var preloadImage = function(url){ return new Promise(function(resolve, reject){ var image = new Image(); image.onload = resolve; image.onerror = reject; image.src = url; }); };
function getFoo(){ return new Promise(function(resolve){ resolve("foo"); }); } function* gen(){ try{ var foo = yield getFoo(); console.log(foo); } catch(e) { console.log(e); } } var it = gen(); (function go(result){ if(result.done) return result.value; return result.value.then(function(value){ return go(it.next(value)); }, function(err){ return go(it.throw(error)); }); })(it.next()); //foo
const sleep = (time) => new Promise(function(resolve){ setTimeout(resolve, time); }); (async () => { for(var i = 0; i < 5; i++){ await sleep(1000); console.log(new Date, i); } await sleep(1000); console.log(new Date, i); })();