深刻了解promise

在上篇文章裏《JavaScript基礎——回調(callback)是什麼》咱們一塊兒學習了回調,明白了回調就是一個在另一個函數執行完後要執行的函數,若是咱們但願異步函數可以像同步函數那樣順序執行,只能嵌套使用回調函數,過多的回調嵌套會使得代碼變得難以理解與維護,爲了不「回調地獄」讓人發狂的行爲,ES6原生引入了promise的模式,經過這種方式,讓咱們代碼看起來像同步代碼,大大簡化了異步編程,簡直是ES6新特性中最讓咱們興奮的特性之一。javascript

什麼是promise?

首先咱們看看promise這個單詞的中文釋義,做爲名稱解釋爲承諾、諾言、誓言、約言,從中文釋義能夠看出,是一個未發生,未來必定會發生的某種東東…… 接下來咱們來看看ECMA委員會怎麼定義Promise的:java

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.ajax

Promise是一個對象,用做佔位符,用於延遲(多是異步)計算的最終結果。編程

簡單的來講,promise就是裝載將來值的容器。其實生活中有不少Promise的場景,設想如下的場景:咱們去快餐店點餐,你點了一份牛肉麪,你掃碼付款後,會拿到一份帶訂單號的收據。訂單號就是快餐店給咱們的一份牛肉麪的承諾(promise),保證了咱們獲得一份牛肉麪。json

因此咱們必定要包管好咱們的訂單收據,由於咱們知道了這個收據表明了咱們將來會有一份牛肉麪,儘管快餐店不能立刻給咱們一份牛肉麪,可是咱們大腦潛意識的把訂單收據當作牛肉麪的「佔位符」了數組

終於,咱們聽到服務員在喊「100號的牛肉麪好了,請到窗口取餐」,而後咱們拿着訂單收據來到窗口遞給服務員,咱們換來了牛肉麪。promise

說了這麼多,簡單點就是,一旦我須要的值準備好了,咱們就用個人承諾值換取這個值自己。bash

可是,還有一種結果,服務員叫到咱們的訂單號,當咱們去拿的時候,服務員會一臉歉意的告訴咱們「十分抱歉,您的牛肉麪賣完了」。做爲顧客的咱們對這個狀況,除了憤怒以外只能換個地吃飯了或者點其餘的。從中咱們看出,將來值還有一個重要的特性:它可能成功也可能失敗。異步

生活的例子很簡單,咱們是否是特別着急如何用代碼編寫Promise呢,在編寫代碼以前,咱們仍是先聊聊——Promise State(承諾狀態,注:暫且這麼翻譯,小編也不知道如何翻譯更好)async

Promise State(承諾狀態)

Promise只會處在如下狀態之一:

Pending(待處理): promise初始化的狀態,正在運行,既未完成也沒有失敗的狀態,此狀態能夠遷移至fulfilled和rejected狀態。

Fulfilled(已完成):若是回調函數實現Promise的resolve回調(稍後介紹),那咱們的promise實現兌現。

Rejected(已拒絕):若是Promise調用過程當中遭到拒絕或者發生異常,那麼咱們的promise被拒絕,處於Rejected(狀態)。

Settled(不變的):Promise若是不處在Pending狀態,狀態就會改變,要不是Fulfilled要不是Rejected這兩種狀態。

Promise的狀態轉換,能夠用下面一張圖進行表示(圖片來源:developer.mozilla.org/en-US/docs/…

Promise vs callback

好比咱們有個需求,須要經過AJAX實現三個請求,第二個和第三個請求都依賴上一個接口的請求,若是使用CallBack的方式,咱們的代碼多是這樣的:

ajaxCall('http://example.com/page1', response1 => { 
 ajaxCall('http://example.com/page2'+response1, response2 => { 
  ajaxCall('http://example.com/page3'+response2, response3 => { 
  console.log(response3) 
  } 
 }) 
})複製代碼複製代碼

你很快就會發現,這種多重嵌套的代碼不但難以理解,並且難以維護,這就是著名的「回調地獄」現象。

若是使用Promise則會讓咱們的大腦更容易接受和理解,代碼顯得簡單扁平化,代碼以下(僞代碼),如何實現ajaxCallPromise稍後介紹:

ajaxCallPromise('http://example.com/page1') 
.then( response1 => ajaxCallPromise('http://example.com/page2'+response1) ) 
.then( response2 => ajaxCallPromise('http://example.com/page3'+response2) ) 
.then( response3 => console.log(response3) )複製代碼複製代碼

你是否是以爲代碼的複雜性忽然下降,代碼看起來更簡單易讀呢,各位是否是特別着急ajaxCallPromise是如何實現的吧,接着看下一小節,會有介紹。

Promise實現——(resolve, reject) 方法

要實現回調函數轉換成Promise,咱們須要使用Promise構造函數,在上一小節,咱們的示例ajaxCallPromise函數返回Promise,咱們能夠自定義resolve實現和reject實現,實現函數以下:

const ajaxCallPromise = url => { 
 return new Promise((resolve, reject) => { 
 // DO YOUR ASYNC STUFF HERE 
 $.ajaxAsyncWithNativeAPI(url, function(data) { 
  if(data.resCode === 200) { 
  resolve(data.message) 
  } else { 
  reject(data.error) 
  } 
 }) 
 }) 
}複製代碼複製代碼

如何理解這段代碼呢?

  1. 首先定義ajaxCallPromise返回類型爲Promise,這意味咱們會實現一個Promise的承諾。
  2. Promise接受兩個函數參數,resolve(成功實現承諾)和reject(異常致使失敗)
  3. resolve和reject這兩個特有的方法,會獲取對應成功或失敗的值
  4. 若是接口請求一切正常,咱們將會接收到resolve函數返回的值
  5. 若是接口請求失敗,咱們將會接收到reject返回的的值

在舉個簡單的例子,若是foo()和bar()函數都實現promise,咱們怎麼書寫代碼呢?

方式一:

foo().then( res => { 
 bar().then( res2 => { 
  console.log('Both done') 
 }) 
})複製代碼複製代碼

方式二(建議這種,簡單易讀)

foo() 
.then( res => bar() ) // bar() returns a Promise 
.then( res => { 
 console.log('Both done') 
})複製代碼複製代碼

.then(onFulfilled, onRejected) 方法

Promise的then()方法容許咱們在任務完成後或拒絕失敗後執行任務,該任務能夠是基於另一個事件或基於回調的異步操做。

Promise的then()方法接收兩個參數,即onFulfilled 和 onRejected 的回調,若是Promise對象完成,則執行onFulfilled回調,若是執行異常或失敗執行onRejected回調。

簡單的來講,onFulfilled回調接收一個參數,及所謂的將來的值,一樣 onRejected 也接收一個參數,顯示拒絕的緣由。讓咱們動下上小節ajaxCallPromise的then()方法:

ajaxCallPromise('http://example.com/page1').then( 
 successData => { console.log('Request was successful') }, 
 failData => { console.log('Request failed' + failData) } 
)複製代碼複製代碼

若是請求過程失敗,第二個函數將會執行輸出而不是第一個函數輸出。

咱們一塊兒再來看個簡單的例子,咱們在setTimeout()實現Promise回調,代碼以下:

const PsetTimeout = duration => { 
 return new Promise((resolve, reject) => { 
  setTimeout( () => { 
   resolve() 
  }, duration); 
 }) 
} 
PsetTimeout(1000) 
.then(() => { 
 console.log('Executes after a second') 
})複製代碼複製代碼

這裏咱們在這裏實現了一個成功狀態後沒有返回值的Promise,若是這樣作,成功返回後將來值將會是 undefined.

catch(onRejected)方法

除了then()方法能夠處理錯誤和異常,使用Promise的catch()方法也能實現一樣的功能,這個方法其實並無什麼特別,只是更容易理解而已。

catch()方法只接收一個參數,及onRejected回調。catch()方法的onRejected回調的調用方式與then()方法的onRejected回調相同。

還記得咱們上小節ajaxCallPromise的then()方法的實現嗎:

ajaxCallPromise('http://example.com/page1').then( 
 successData => { console.log('Request was successful') }, 
 failData => { console.log('Request failed' + failData) } 
)複製代碼複製代碼

咱們還可使用catch()方法進行捕獲異常或拒絕,效果是一致的。

ajaxCallPromise('http://example.com/page1’) .then(successData => console.log('Request was successful’)) 
.catch(failData => console.log('Request failed' + failData));複製代碼複製代碼

Promise.resolve(value)方法

Promise的resolve()方法接收成功返回值並返回一個Promise對象,用於將來值的傳遞,將值傳遞給.then(onFulfilled, onRejected) 的onFulfilled回調中。resolve()方法能夠用於將將來值轉化成Promise對象,下面的一段代碼演示瞭如何使用Promise.resolve()方法:

const p1 = Promise.resolve(4); 
p1.then(function(value){ 
 console.log(value); 
}); //passed a promise object 
Promise.resolve(p1).then(function(value){ 
 console.log(value); 
}); 
Promise.resolve({name: "Eden"}) 
.then(function(value){ 
 console.log(value.name); 
});複製代碼複製代碼

控制檯將會輸出如下內容:

4 
4 
Eden複製代碼複製代碼

Promise.reject(value)方法

Promise.reject(value)方法可上小節Promise.resolve(value)相似,惟一不一樣的是將值傳遞給.then(onFulfilled, onRejected) 的onRejected回調中,同時Promise.reject(value)主要用來進行調試,而不是將值轉換成Promise對象。

讓咱們看看下面一段代碼,看看如何使用Promise.reject(value)方法:

const p1 = Promise.reject(4); 
p1.then(null, function(value){ 
console.log(value); 
}); 
Promise.reject({name: "Eden"}) 
.then(null, function(value){ 
 console.log(value.name); 
});複製代碼複製代碼

控制檯將輸出:

4 
Eden複製代碼複製代碼

Promise.all(iterable) 方法

該方法能夠傳入一個能夠迭代Promise對象,好比數組並返回一個Promise對象,當全部的Promise迭代對象成功返回後,整個Promise才能返回成功狀態的值。

好了,咱們一塊兒看看怎麼實現Promise.all(iterable) 方法:

const p1 = new Promise(function(resolve, reject){ 
 setTimeout(function(){ 
 resolve(); 
 }, 1000); 
}); 
const p2 = new Promise(function(resolve, reject){ 
setTimeout(function(){ 
 resolve(); 
 }, 2000); 
}); 
const arr = [p1, p2]; 
Promise.all(arr).then(function(){ 
console.log("Done"); //"Done" is logged after 2 seconds 
});複製代碼複製代碼

特別須要注意的一點,只要迭代數組中,只要任意一個進入失敗狀態,那麼該方法返回的對象也會進入失敗狀態,並將那個進入失敗狀態的錯誤信息做爲本身的錯誤信息,示例代碼以下:

const p1 = new Promise(function(resolve, reject){ 
 setTimeout(function(){ 
 reject("Error"); 
 }, 1000); 
}); 
const p2 = new Promise(function(resolve, reject){ 
 setTimeout(function(){ 
 resolve(); 
 }, 2000); 
}); 
const arr = [p1, p2]; 
Promise.all(arr).then(null, function(reason){ 
console.log(reason); //"Error" is logged after 1 second 
});複製代碼複製代碼

Promise.race(iterable) 方法

與Promise.all(iterable) 不一樣的是,Promise.race(iterable) 雖然也接收包含若干個Promise對象的可迭代對象,不一樣的是這個方法會監聽全部的Promise對象,並等待其中的第一個進入完成或失敗狀態的Promise對象,一旦有Promise對象知足,整個Promise對象將返回這個Promise對象的成功狀態或失敗狀態,下面的示例展現了返回第一個成功狀態的值:

var p1 = new Promise(function(resolve, reject){ 
setTimeout(function(){ 
resolve("Fulfillment Value 1"); 
}, 1000); 
}); 
var p2 = new Promise(function(resolve, reject){ 
setTimeout(function(){ 
resolve("fulfillment Value 2"); 
}, 2000); 
}); 
var arr = [p1, p2]; 
Promise.race(arr).then(function(value){ 
console.log(value); //Output "Fulfillment value 1" 
}, function(reason){ 
console.log(reason);複製代碼複製代碼

用Promise改寫上篇文章的回調方法

讀過《JavaScript基礎——回調(callback)是什麼》文章同窗,文章的最後咱們用回調函數實現了一個真實的業務場景——用NodeJs實現從論壇帖子列表中顯示其中的一個帖子的信息及留言列表信息,若是使用本篇文章學習到的內容,咱們如何實現呢, 代碼以下:

index.js

const fs = require('fs'); 
const path = require('path'); 
const postsUrl = path.join(__dirname, 'db/posts.json'); 
const commentsUrl = path.join(__dirname, 'db/comments.json'); 
//return the data from our file 
function loadCollection(url) { 
 return new Promise(function(resolve, reject) { 
  fs.readFile(url, 'utf8', function(error, data) { 
   if (error) { 
    reject('error'); 
   } else { 
    resolve(JSON.parse(data)); 
   } 
  }); 
 }); 
} 
//return an object by id 
function getRecord(collection, id) { 
 return new Promise(function(resolve, reject) { 
  const data = collection.find(function(element){ 
   return element.id == id; 
  }); 
  resolve(data); 
 }); 
} 
//return an array of comments for a post 
function getCommentsByPost(comments, postId) { 
 return comments.filter(function(comment){ 
  return comment.postId == postId; 
 }); 
} 
//initialization code 
loadCollection(postsUrl) 
.then(function(posts){ 
 return getRecord(posts, "001"); 
}) 
.then(function(post){ 
 console.log(post); 
 return loadCollection(commentsUrl); 
}) 
.then(function(comments){ 
 const postComments = getCommentsByPost(comments, "001"); 
 console.log(postComments); 
}) 
.catch(function(error){ 
 console.log(error); 
});複製代碼
相關文章
相關標籤/搜索