在上篇文章裏《JavaScript基礎——回調(callback)是什麼》咱們一塊兒學習了回調,明白了回調就是一個在另一個函數執行完後要執行的函數,若是咱們但願異步函數可以像同步函數那樣順序執行,只能嵌套使用回調函數,過多的回調嵌套會使得代碼變得難以理解與維護,爲了不「回調地獄」讓人發狂的行爲,ES6原生引入了promise的模式,經過這種方式,讓咱們代碼看起來像同步代碼,大大簡化了異步編程,簡直是ES6新特性中最讓咱們興奮的特性之一。javascript
首先咱們看看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只會處在如下狀態之一:
Pending(待處理): promise初始化的狀態,正在運行,既未完成也沒有失敗的狀態,此狀態能夠遷移至fulfilled和rejected狀態。
Fulfilled(已完成):若是回調函數實現Promise的resolve回調(稍後介紹),那咱們的promise實現兌現。
Rejected(已拒絕):若是Promise調用過程當中遭到拒絕或者發生異常,那麼咱們的promise被拒絕,處於Rejected(狀態)。
Settled(不變的):Promise若是不處在Pending狀態,狀態就會改變,要不是Fulfilled要不是Rejected這兩種狀態。
Promise的狀態轉換,能夠用下面一張圖進行表示(圖片來源:developer.mozilla.org/en-US/docs/…)
好比咱們有個需求,須要經過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,咱們須要使用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)
}
})
})
}複製代碼複製代碼
如何理解這段代碼呢?
在舉個簡單的例子,若是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')
})複製代碼複製代碼
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.
除了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()方法接收成功返回值並返回一個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.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對象,好比數組並返回一個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.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);複製代碼複製代碼
讀過《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);
});複製代碼