在第2章?[Promise.all](http://liubin.github.io/promises-book/#ch2-promise-all)?中,我們已經學習了如何讓多個promise對象同時開始執行的方法。
但是?`Promise.all`?方法會同時運行多個promise對象,如果想進行在A處理完成之后再開始B的處理,對于這種順序執行的話?`Promise.all`就無能為力了。
此外,在同一章的[Promise和數組](http://liubin.github.io/promises-book/#ch2-promise-and-array)?中,我們也介紹了一種效率不是特別高的,使用了?[重復使用多個then的方法](http://liubin.github.io/promises-book/#multiple-xhr.js)?來實現如何按順序進行處理。
在本小節中,我們將對如何在Promise中進行順序處理進行介紹。
## 4.8.1\. 循環和順序處理
在?[重復使用多個then的方法](http://liubin.github.io/promises-book/#multiple-xhr.js)?中的實現方法如下。
~~~
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var request = {
comment: function getComment() {
return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
},
people: function getPeople() {
return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
}
};
function main() {
function recordValue(results, value) {
results.push(value);
return results;
}
// [] 用來保存初始化的值
var pushValue = recordValue.bind(null, []);
return request.comment().then(pushValue).then(request.people).then(pushValue);
}
// 運行示例
main().then(function (value) {
console.log(value);
}).catch(function(error){
console.error(error);
});
~~~
使用這種寫法的話那么隨著?`request`?中元素數量的增加,我們也需要不斷增加對?`then`?方法的調用
因此,如果我們將處理內容統一放到數組里,再配合for循環進行處理的話,那么處理內容的增加將不會再帶來什么問題。首先我們就使用for循環來完成和前面同樣的處理。
promise-foreach-xhr.js
~~~
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var request = {
comment: function getComment() {
return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
},
people: function getPeople() {
return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
}
};
function main() {
function recordValue(results, value) {
results.push(value);
return results;
}
// [] 用來保存初始化值
var pushValue = recordValue.bind(null, []);
// 返回promise對象的函數的數組
var tasks = [request.comment, request.people];
var promise = Promise.resolve();
// 開始的地方
for (var i = 0; i < tasks.length; i++) {
var task = tasks[i];
promise = promise.then(task).then(pushValue);
}
return promise;
}
// 運行示例
main().then(function (value) {
console.log(value);
}).catch(function(error){
console.error(error);
});
~~~
使用for循環的時候,如同我們在?[專欄: 每次調用then都會返回一個新創建的promise對象](http://liubin.github.io/promises-book/#then-return-new-promise)?以及?[Promise和方法鏈](http://liubin.github.io/promises-book/#promise-and-method-chain)?中學到的那樣,每次調用?[Promise#then](http://liubin.github.io/promises-book/#promise.then)?方法都會返回一個新的promise對象。
因此類似?`promise = promise.then(task).then(pushValue);`?的代碼就是通過不斷對promise進行處理,不斷的覆蓋?`promise`?變量的值,以達到對promise對象的累積處理效果。
但是這種方法需要?`promise`?這個臨時變量,從代碼質量上來說顯得不那么簡潔。
如果將這種循環寫法改用?`Array.prototype.reduce`?的話,那么代碼就會變得聰明多了。
## 4.8.2\. Promise chain和reduce
如果將上面的代碼用?`Array.prototype.reduce`?重寫的話,會像下面一樣。
promise-reduce-xhr.js
~~~
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var request = {
comment: function getComment() {
return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
},
people: function getPeople() {
return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
}
};
function main() {
function recordValue(results, value) {
results.push(value);
return results;
}
var pushValue = recordValue.bind(null, []);
var tasks = [request.comment, request.people];
return tasks.reduce(function (promise, task) {
return promise.then(task).then(pushValue);
}, Promise.resolve());
}
// 運行示例
main().then(function (value) {
console.log(value);
}).catch(function(error){
console.error(error);
});
~~~
這段代碼中除了?`main`?函數之外的其他處理都和使用for循環的時候相同。
`Array.prototype.reduce`?的第二個參數用來設置盛放計算結果的初始值。在這個例子中,?`Promise.resolve()`?會賦值給?`promise`?,此時的?`task`?為?`request.comment`?。
在reduce中第一個參數中被?`return`?的值,則會被賦值為下次循環時的?`promise`?。也就是說,通過返回由?`then`?創建的新的promise對象,就實現了和for循環類似的?[Promise chain](http://liubin.github.io/promises-book/#promise-chain)?了。
> 下面是關于?`Array.prototype.reduce`?的詳細說明。
> * [Array.prototype.reduce() - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)
> * [azu / Array.prototype.reduce Dance - Glide](http://glide.so/azu/6919649)
使用reduce和for循環不同的地方是reduce不再需要臨時變量?`promise`?了,因此也不用編寫?`promise = promise.then(task).then(pushValue);`?這樣冗長的代碼了,這是非常大的進步。
雖然?`Array.prototype.reduce`?非常適合用來在Promise中進行順序處理,但是上面的代碼有可能讓人難以理解它是如何工作的。
因此我們再來編寫一個名為?`sequenceTasks`?的函數,它接收一個數組作為參數,數組里面存放的是要進行的處理Task。
從下面的調用代碼中我們可以非常容易的從其函數名想到,該函數的功能是對?`tasks`?中的處理進行順序執行了。
~~~
var tasks = [request.comment, request.people];
sequenceTasks(tasks);
~~~
## 4.8.3\. 定義進行順序處理的函數
基本上我們只需要基于?[使用reduce的方法](http://liubin.github.io/promises-book/#promise-reduce-xhr.js)?重構出一個函數。
promise-sequence.js
~~~
function sequenceTasks(tasks) {
function recordValue(results, value) {
results.push(value);
return results;
}
var pushValue = recordValue.bind(null, []);
return tasks.reduce(function (promise, task) {
return promise.then(task).then(pushValue);
}, Promise.resolve());
}
~~~
需要注意的一點是,和?`Promise.all`?等不同,這個函數接收的參數是一個函數的數組。
為什么傳給這個函數的不是一個promise對象的數組呢?這是因為promise對象創建的時候,XHR已經開始執行了,因此再對這些promise對象進行順序處理的話就不能正常工作了。
因此?`sequenceTasks`?將函數(該函數返回一個promise對象)的數組作為參數。
最后,使用?`sequenceTasks`?重寫最開始的例子的話,如下所示。
promise-sequence-xhr.js
~~~
function sequenceTasks(tasks) {
function recordValue(results, value) {
results.push(value);
return results;
}
var pushValue = recordValue.bind(null, []);
return tasks.reduce(function (promise, task) {
return promise.then(task).then(pushValue);
}, Promise.resolve());
}
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
var request = {
comment: function getComment() {
return getURL('http://azu.github.io/promises-book/json/comment.json').then(JSON.parse);
},
people: function getPeople() {
return getURL('http://azu.github.io/promises-book/json/people.json').then(JSON.parse);
}
};
function main() {
return sequenceTasks([request.comment, request.people]);
}
// 運行示例
main().then(function (value) {
console.log(value);
}).catch(function(error){
console.error(error);
});
~~~
怎樣,?`main()`?中的流程是不是更清晰易懂了。
如上所述,在Promise中,我們可以選擇多種方法來實現處理的按順序執行。
* [循環使用then調用的方法](http://liubin.github.io/promises-book/#multiple-xhr.js)
* [使用for循環的方法](http://liubin.github.io/promises-book/#promise-foreach-xhr.js)
* [使用reduce的方法](http://liubin.github.io/promises-book/#promise-reduce-xhr.js)
* [分離出順序處理函數的方法](http://liubin.github.io/promises-book/#promise-sequence.js)
但是,這些方法都是基于JavaScript中對數組及進行操作的for循環或?`forEach`?等,本質上并無大區別。 因此從一定程度上來說,在處理Promise的時候,將大塊的處理分成小函數來實現是一個非常好的實踐。
## 4.8.4\. 總結
在本小節中,我們對如何在Promise中進行和?[`Promise.all`](http://liubin.github.io/promises-book/#Promise.all)?相反,按順序讓promise一個個進行處理的實現方式進行了介紹。
為了實現順序處理,我們也對從過程風格的編碼方式到自定義順序處理函數的方式等實現方式進行了介紹,也再次強調了在Promise領域我們應遵循將處理按照函數進行劃分的基本原則。
在Promise中如果還使用了Promise chain將多個處理連接起來的話,那么還可能使源代碼中的一條語句變得很長。
這時候如果我們回想一下這些編程的基本原則進行函數拆分的話,代碼整體結構會變得非常清晰。
此外,Promise的構造函數以及?`then`?都是高階函數,如果將處理分割為函數的話,還能得到對函數進行靈活組合使用的副作用,意識到這一點對我們也會有一些幫助的。
> 高階函數指的是一個函數可以接受其參數為函數對象的實例
- 前言
- 第一章 - 什么是Promise
- 1.1. 什么是Promise
- 1.2. Promise簡介
- 1.3. 編寫Promise代碼
- 第二章 - 實戰Promise
- 2.1. Promise.resolve
- 2.2. Promise.reject
- 2.3. 專欄: Promise只能進行異步操作?
- 2.4. Promise#then
- 2.5. Promise#catch
- 2.6. 專欄: 每次調用then都會返回一個新創建的promise對象
- 2.7. Promise和數組
- 2.8. Promise.all
- 2.9. Promise.race
- 2.10. then or catch?
- 第三章 - Promise測試
- 3.1. 基本測試
- 3.2. Mocha對Promise的支持
- 3.3. 編寫可控測試(controllable tests)
- 第四章 - Advanced
- 4.1. Promise的實現類庫(Library)
- 4.2. Promise.resolve和Thenable
- 4.3. 使用reject而不是throw
- 4.4. Deferred和Promise
- 4.5. 使用Promise.race和delay取消XHR請求
- 4.6. 什么是 Promise.prototype.done ?
- 4.7. Promise和方法鏈(method chain)
- 4.8. 使用Promise進行順序(sequence)處理
- 第五章 - Promises API Reference
- 5.1. Promise#then
- 5.2. Promise#catch
- 5.3. Promise.resolve
- 5.4. Promise.reject
- 5.5. Promise.all
- 5.6. Promise.race
- 第六章 - 用語集
- 第七章 - 參考網站
- 第八章 - 關于作者
- 第九章 - 關于譯者