Promise 對象
Promise 對象用於處理異步請求,保存一個異步操作最終完成(或失敗)的結果
語法
new Promise( /* executor */ function(resolve, reject) {...} ); /* 來自谷歌翻譯 promise:承諾,約定 resolve:解決,決定,下決心 reject:拒絕,駁回,抵制 */
參數:
promise 構造函數接受一個 executor 函數作為參數,該函數的兩個參數分別是 resolve 和 reject,它們是兩個函數(executor 函數 在 Promise 構造函數返回新對象之前被調用)。
resolve 函數被調用時,將 promise 對象從 “未完成” 變為 “成功” (即 pending --> fulfilled)
reject 函數被調用時,將 promise 對象從 “未完成” 變為 “失敗” (即 pending --> rejected)
描述
promise 對象是一個代理對象(代理一個值),被代理的值在 promise 對象創建時可能是未知的。它允許你為異步操作的成功和失敗分別綁定相應的處理方法,使得異步方法可以像同步方法那樣返回值,但不是立即返回最終執行結果,而是一個能代表未來出現的結果的 promise 對象。
上面提到過,一個 promise 對象有三個狀態:
? pending:初始狀態,不是成功或失敗狀態
? fulfilled:成功完成
? rejected:失敗
pending 狀態可能觸發 fulfilled 狀態並傳遞一個值給相應的狀態處理方法,也可能觸發 rejected 狀態並傳遞失敗信息。當其中任一種情況發生時,promise 對象的 then 方法綁定的處理方法就會被調用。
(then 方法包含兩個參數:onfulfilled 和 onrejected,也都是函數。當 promise 對象的狀態為 fulfilled 時調用,調用 then 的 onfulfilled 方法;反之,調用 onrejected 方法。所以異步操作的完成和綁定處理方法之間不存在競爭)
then 方法的使用語法:
promise.then(function(value) { // onfulfilled }, function(error) { // 第二個參數是可選的,不一定要提供 // onrejected });
示例:
let myFirstPromise = new Promise(function(resolve, reject){ // 當異步代碼執行成功時,調用resolve()方法;失敗時,調用reject()方法 // 此處,使用定時器來模擬異步代碼,實際編碼可能是XHR請求或HTML5的一些API方法 setTimeout(function(){ //resolve(‘成功!‘) //代碼執行成功,調用resolve()方法 resolve(‘成功!‘) }, 2000) }) myFirstPromise.then(function(successMessage){ // successMessage 是上面調用resolve()方法傳入的值 // successMessage 參數不一定非要是字符串類型,這裏只是舉個例子 console.log(‘Yay!‘+successMessage) }) console.log(‘看看我的位置在哪裏?‘)
運行結果:
想要某個函數擁有 promise 功能,只需要讓其返回一個 promise 即可
function timeout (ms) { return new Promise ((resolve, reject) => { setTimeout(resolve, ms, ‘done‘) /* setTimeout(function, milliseconds, param1, param2, ...) function 必需, milliseconds 可選,默認為0。執行或調用function需要等待的時間 param1,param2 可選,傳給執行函數的其它參數(IE9- 不支持該參數) */ }) } timeout(2000).then((value) => { console.log(value) }) console.log(‘立即出現……‘)
上面代碼中,timeout 方法返回一個 Promise實例,表示一段時間以後才會發生的結果。過了指定的時間(參數ms)以後,Promise 實例狀態變成 fulfilled,就會觸發 then方法的onfulfilled 方法。運行結果如下:
Promise 新建後就會立即執行
let promise = new Promise((resolve, reject) => { console.log(‘立即執行 Promise !‘) resolve() }) promise.then(() => { console.log(‘fulfilled‘) }) console.log(‘我什麽時候出現呢?‘)
上面代碼中,Promise 新建後立即執行,所以最先輸出的是 “立即執行 Promise !”。then 方法指定的回調函數將在當前腳本所有同步任務執行完以後才會執行,所以 “fulfilled” 最後輸出。運行結果如下:
下面是一個用 Promise 對象實現異步加載圖像的例子
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <script> function imgLoad(url) { // 使用 Promise() 構造函數創建 promise 實例, 接受兩個參數,resolve 和 reject 函數 return new Promise ( (resolve, reject) => { /******************** 異步加載圖片的標準方法 ************************/ // 創建 XMLHTTPRequest 對象 var xhr = new XMLHttpRequest() /* open(method,url,async) 規定請求的類型、URL以及是否異步處理請求 method:請求的類型,GET 或 POST url:文件在服務器上的位置 async:true --> 異步,false ---> 同步 */ xhr.open(‘GET‘, url) /* BLOB (binary large object),表示二進制大對象 在實際Web應用中,Blob更多是圖片二進制形式的上傳與下載,雖然其可以實現幾乎任意文件的二進制傳輸 responseType設為blob,表示服務器傳回的是二進制對象 */ xhr.responseType = ‘blob‘ // 處理請求時,檢查是否成功 xhr.onload = function () { if (xhr.status === 200) { // 如果成功的話,把響應內容作為參數傳給resolve 方法 resolve(xhr.response) } else { // 如果失敗的話,把服務器返回的錯誤狀態碼(status ->響應的 HTTP 狀態,statusText ->HTTP 狀態的說明,如 200 對應的statusText就是 OK)作為錯誤信息展示出來 reject(Error(‘Image didn\‘t load successfully; error code:‘ + xhr.statusText)) } } // 檢測錯誤 xhr.onerror = function () { // 假如一開始就全部請求失敗,有可能是網絡錯誤 reject(Error(‘There was a network error.‘)) } /* send(string) 將請求發送到服務器 string:僅用於 POST 請求 */ xhr.send() }) } // 獲取body,然後創建一個新的 image 對象 var body = document.querySelector(‘body‘), myImg = new Image(); // 調用 imgLoad 方法並傳入我們想獲取的 url,在 imgLoad 方法後面鏈式調用 then 方法 imgLoad(‘Penguins.jpg‘).then( (response) => { // 創建一個新的 URL 對象 var imageURL = window.URL.createObjectURL(response) myImg.src = imageURL body.appendChild(myImg) }, (Error) => { console.log(Error) }) </script> </body> </html>
用Apache服務器打開效果:(成功)
雙擊網頁打開效果:(失敗)
如果調用 resolve 函數和 reject 函數時帶有參數,那麽,它們的參數會被傳遞給回調函數。reject 函數的參數通常是 Error 對象的實例,表示拋出的錯誤;resolve 函數的參數除了是正常值以外,還可以是另一個 Promise,如:
var p1 = new Promise(function (resolve, reject) { // ... }); var p2 = new Promise(function (resolve, reject) { // ... resolve(p1); })
上例中,p1的狀態決定了p2的狀態,如果p1的狀態是 pending,那麽,p2的回調函數就會等待p1的狀態改變;如果p1的狀態已經是 fulfilled 或 rejected,那麽,p2的回調函數就會立即執行
var p1 = new Promise ( (resolve, reject) => { setTimeout( () => reject(new Error(‘fail‘)), 3000) }) var p2 = new Promise ( (resolve, reject) => { setTimeout( () => resolve(p1), 1000) }) p2.then( result => console.log(result)) .catch( error => console.log(error)) // Error: fail
上面代碼中,p1是一個 Promise,3s後變成一個 rejected。p2的狀態在 1s後改變,resolve 方法返回的是 p1。由於 p2 返回的是另一個 Promise,導致p2自己的狀態無效了,由 p1的狀態決定p2的狀態,所以後面的 then語句都變成了針對 p1。又過了2秒,p1變為rejected,導致觸發catch方法指定的回調函數。
註意,調用resolve或reject並不會終結 Promise 的參數函數的執行
new Promise((resolve, reject) => { resolve(1); console.log(2); }).then(r => { console.log(r); }); //2 //1
上面代碼中,調用resolve(1)以後,後面的console.log(2)還是會執行,並且會首先打印出來。這是因為立即 resolved 的 Promise 是在本輪事件循環的末尾執行,總是晚於本輪循環的同步任務
下例展示了 Promise 的一些機制。 testPromise() 方法在每次點擊 <button> 按鈕時被調用,該方法會創建一個promise 對象,使用 window.setTimeout() 讓Promise等待 1-3 秒不等的時間來填充數據(通過Math.random()方法)。
Promise 的值的填充過程都被日誌記錄(logged)下來,這些日誌信息展示了方法中的同步代碼和異步代碼是如何通過Promise完成解耦的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link href="https://cdn.bootcss.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <button type="button" class="btn btn-primary" onclick="testPromise()">button</button> <div id="log"></div> </div> <script> let promiseCount = 0; function testPromise () { let thisPromiseCount = ++promiseCount, log = document.getElementById(‘log‘); /* insertAdjacentHTML() 將指定的文本解析為HTML或XML,並將結果節點插入到DOM樹中的指定位置。它不會重新解析它正在使用的元素, 因此它不會破壞元素內的現有元素。這避免了額外的序列化步驟,使其比直接innerHTML操作更快。 語法:element.insertAdjacentHTML(position, text) position是相對於元素的位置,‘beforebegin‘ -> 元素自身的前面 text是要被解析為HTML或XML,並插入到DOM樹中的字符串 */ log.insertAdjacentHTML(‘beforeend‘, thisPromiseCount+ ‘) Started(<small>Sync code started</small>)<br>‘) // 創建一個 Promise 實例,用於計數,從1開始 let p1 = new Promise( (resolve, reject) => { log.insertAdjacentHTML(‘beforeend‘, thisPromiseCount+ ‘) Promised started(<small>Async code started</small>)<br>‘) // 用定時器模擬異步代碼 window.setTimeout(function (){ // 狀態從 pending 變成 fulfilled resolve(thisPromiseCount) }, Math.random()*2000+1000 ) }) p1.then(function (val) { // 記錄 fulfillment 的值 log.insertAdjacentHTML(‘beforeend‘, val+ ‘) Promise fulfilled(<small>Async code started</small>)<br>‘) }).catch( // 當 promise 狀態變成rejected,調用catch 方法 (reason) => { // 記錄 rejection 的原因 console.log(‘Handle rejected promise (‘+reason+‘) here.‘) } ) log.insertAdjacentHTML(‘beforeend‘, thisPromiseCount + ‘) Promise made (<small>Sync code terminated</small>)<br/>‘) } </script> </body> </html>
快速點擊按鈕多次可以觀察到Promises填充值的過程,運行結果如下:
Promise 對象