在JavaScript中,回調函數是處理異步編程的經常使用解決方案,但層層嵌套的回調金字塔(以下代碼所示)一直受人詬病,由於不只在視覺上更加混亂,並且在管理上也更爲複雜。編程
setTimeout(() => { var reason = "成功執行"; setTimeout(() => { console.log(reason); }, 500); }, 500);
Promise是ES6新增的特性,能更合理的控制和追蹤異步操做。它是一個包含狀態、可繼承的對象,不只能管理而不是依賴回調,還能以同步的方式傳遞異步的計算結果,從而避免陷入回調金字塔的泥潭中。鏈式(即串聯起來)的Promise讓代碼有更高的可讀性和更便捷的調試性。下面會用Promise實現上一個示例的功能。promise
var promise = new Promise(function(resolve, reject) { setTimeout(() => { resolve("成功執行"); }, 500); }); promise.then(function(value) { setTimeout(() => { console.log(value); }, 500); });
示例只是爲了能對Promise有個初步的認識,其中涉及的Promise的建立、then()方法等概念,都將在接下來的章節中作詳細的講解。異步
Promise依據其狀態的變化,讓異步操做變得有序,而Promise有三種互斥的狀態可供選擇,具體以下所列。異步編程
(1)pending:等待中,初始狀態,此時還未處理(Promise中的)異步操做。函數
(2)fulfilled:已完成,異步操做成功時的狀態。spa
(3)rejected:已拒絕,異步操做失敗時的狀態。調試
每一個Promise只能維護一個狀態,而且狀態只會朝一個方向變化,即從pending變爲fulfilled或rejected,而fulfilled不能變爲rejected,反之也同樣,這種處理狀態的行爲也叫決議。注意,Promise會在內部處理狀態的變化,而且因爲ES6對外沒有暴露訪問Promise狀態的屬性或方法,所以沒法在外部判斷Promise當前處在哪一個狀態。code
若是要使用Promise,那麼須要先初始化,能夠經過構造函數的方式建立一個Promise實例,以下所示。對象
var promise = new Promise(function(resolve, reject) { /* executor */ });
構造函數Promise()能接收一個執行器(executor),即帶有resolve和reject兩個參數的函數,執行器會在構造函數返回新實例前被調用。它的兩個參數也是函數,分別適合不一樣的語境,具體以下所列。blog
(1)在執行器中的異步操做完成時會調用resolve()函數,當前Promise的狀態會根據它的參數發生變化。當參數爲空或非Promise時,當前狀態變成fulfilled;當參數是Promise時,當前Promise的狀態和參數的相同。
(2)在執行器中的異步操做錯誤時會調用reject()函數,當前Promise的狀態會變成rejected。
resolve()和reject()都能接收一個參數(即決議結果),前者的參數能夠是本次操做的結果;然後者的參數能夠是操做失敗的理由,它們都會傳遞給下一個異步操做。
在生成Promise實例以後,就能經過then()方法綁定狀態變化後的回調函數(即處理方法),以下代碼所示,此處是異步操做同步化的關鍵。
promise.then(function(value) { // success }, function(reason) { // failure });
then()方法的兩個參數,可分別指定狀態變成fulfilled和rejected後的回調函數,而這兩個回調函數的參數分別來自於resolve()和reject()函數。經過then()方法的這兩個回調函數就能清晰的反饋出異步操做是否成功執行了。
因爲then()方法的返回值是一個新的Promise實例,所以能夠鏈式調用then()方法,按順序綁定回調函數,以下所示。
var chain = new Promise(function(resolve, reject) { reject("error"); }); chain.then(null, function(reason) { console.log(reason); //"error" return "end"; }) .then(function(value) { console.log(value); //"end" });
雖然第一個then()方法中的已完成的回調函數是null,但並不會終止數據的傳遞,仍然是先輸出「error」,再輸出「end」。之因此是這樣的輸出順序,與回調函數的執行順序有關。在then()方法鏈中,當前Promise的狀態會決定下一個then()方法執行哪一個回調函數,而這個狀態又會受回調函數和它的返回值的影響,具體以下所列。
(1)當返回值是一個非Promise的值時,其狀態會變成fulfilled。
(2)當回調函數拋出一個錯誤時,其狀態會變成rejected。
(3)當返回值是一個Promise時,其狀態與返回值的相同。
下面有一個示例,描述了第三種狀況,其中Promise.resolve()建立了已完成的Promise,至關於新建立一個在執行器中調用resolve()函數的Promise;Promise.reject()建立了已拒絕的Promise;catch()方法能處理拒絕的回調函數。這些都將在隨後的章節中作詳細介紹。
var chain = new Promise(function(resolve, reject) { resolve(); }); chain.then(function(value) { return Promise.resolve("fulfilled"); //至關於 return new Promise(function(resolve) { resolve("fulfilled"); }); }) .then(function(value) { console.log(value); //"fulfilled" return Promise.reject("rejected"); }) .catch(function(reason) { console.log(reason); //"rejected" });
包含then()方法的對象被稱爲thenable,全部的Promise都是thenable,下面是一個自定義的thenable,then()方法的參數含義與Promise中的相同。
let tha = { then(resolve, reject) { reject("thenable"); } };
Promise.resolve()能接收一個thenable,並返回一個新的Promise實例,將上一個示例的tha對象傳遞給它,以下所示。
Promise.resolve(tha) .catch(function(reason) { console.log(reason); //thenable }).then(function() { console.log("end"); });
Promise.resolve()能將thenable轉換成已完成或已拒絕的Promise,其最終的狀態取決於thenable的then()方法,像這個示例中的then()方法調用了reject()函數,所以新的Promise的狀態是已拒絕(rejected)。
Promise的catch()方法能夠捕獲並處理前一個異步操做中拋出的錯誤,它能接收一個已拒絕的回調函數(onRejected),其行爲至關於調用一個忽略已完成的回調函數的then()方法,例如像下面這樣第一個參數傳null或undefined。
catch(onRejected) //至關於 then(null, onRejected) then(undefined, onRejected)
下面是一個使用了catch()方法的例子,先在執行器中拋出一個錯誤,而後在catch()方法中處理。
var error = new Promise(function(resolve, reject) { throw "error info"; }); error.catch(function(reason) { console.log(reason); //"error info" });
若是Promise的狀態在拋出錯誤以前被改變,那麼這個錯誤就不能被catch()方法捕獲,以下所示。
var error = new Promise(function(resolve, reject) { resolve(); throw "error info"; }); error.catch(function(reason) { console.log(reason); //不會輸出 });
在執行器中,因爲resolve()函數在throw語句以前被調用,所以「error info」這句錯誤理由就不能在catch()方法中輸出。
在鏈式的Promise中,一旦發生錯誤,那麼這個錯誤在沒被捕獲前,會一直傳遞下去。爲了確保全部的錯誤都能被處理,可在鏈的末尾加上catch()方法。