## Promise的歷史
在Promise出現之前,如果大家需要實現異步操作,通用的做法是事件加上回調函數,如果我們有多個異步操作需要嵌套執行的話,那么代碼將變得非常難于閱讀。讓我們來看一個具體的代碼例子。在下面的例子中,我們首先通過Http request調用一個web service,然后將從web service收到的數據寫入一個本地文件。從任務的角度來看,這是一個非常簡單的任務,但是當你第一次看到這個代碼的時候,一定覺得頭很暈,因為在這段代碼中
* 對web service進行調用的代碼和寫文件的代碼混雜在一起(寫文件的代碼嵌套在`end`事件的回調函數中),造成代碼模塊不清晰,閱讀和理解起來比較費勁。
* 對錯誤的處理分散在代碼的各個地方,而且錯誤處理的實現方式都不一樣。對web service進行調用的代碼,通過監聽`error`事件來處理錯誤,并且將錯誤輸出到控制套;而寫文件的代碼,是通過回調函數來處理錯誤,并將錯誤通過`throw`語句拋出。
```javascript
var http = require("http");
var fs = require("fs");
var querystring = require("querystring");
var postData = querystring.stringify({
'msg' : 'Hello World!'
});
var options = {
hostname: '127.0.0.1',
port: 8080,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
var req = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
var dataReceived = ""
res.on('data', (chunk) => {
dataReceived = dataReceived + chunk.toString();
console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.')
//now we try to write the message to a file
fs.writeFile("temp.txt", dataReceived, function(err){
if (err){
throw err;
}
console.log("Write file temp.txt succ");
});
})
});
req.on('error', (e) => {
console.log(`problem with request: ${e.message}`);
});
// write data to request body
req.write(postData);
req.end();
```
接下來,讓我們使用Promise重寫上面的代碼。在重寫的代碼中,我們可以看到:
* 對web service進行調用的代碼和寫文件的代碼完全分離開了,代碼結構變得非常清晰。在閱讀代碼的過程中,不會再被不相關的代碼所干擾。
* Promise對象對外提供了統一的回調函數接口(resolve和reject回調函數 ),在重寫的代碼中,我們可以很容易的把對web service的請求分裝到一個模塊中,從而對外隱藏Http Request的所有細節。
* 通過Promise對象的封裝,對錯誤代碼的處理被統一了,都是通過對`reject`函數調用來說明異步操作過程中有錯誤發生,而且錯誤被集中到`.catch`代碼段進行了處理(錯誤都被輸出到了控制臺)。
* 通過Promise的封裝,異步操作的代碼變得和同步操作代碼很像,更方便其他人理解代碼的處理邏輯。
```javascript
'use strict';
var http = require("http");
var fs = require("fs");
var querystring = require("querystring");
var postData = querystring.stringify({
'msg' : 'Hello World!'
});
var options = {
hostname: '127.0.0.1',
port: 8080,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length
}
};
var pHttpRequest = new Promise(function(resolve, reject){
let req = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
let dataReceived = ""
res.on('data', (chunk) => {
dataReceived = dataReceived + chunk.toString();
});
res.on('end', () => {
resolve(dataReceived);
})
});
req.on('error', function(e){
reject(e);
});
req.write(postData);
req.end();
})
pHttpRequest.then(
//http request promise成功時候的處理
//在http request promise成功的時候,開始處理寫文件操作
function(dataReceived){
return new Promise(function(resolve, reject){
fs.writeFile("temp.txt", dataReceived, function(e){
if (e) reject(e);
else resolve("Write file succ");
});
})
}
).then(
//writeFile promise成功時候的處理
function(msg){
console.log(msg);
}
).catch(
//全局錯誤處理
function(err){
console.log("some error happen(" + err + ")");
}
)
```
正式基于Promise對象有上面所說的這些優點,ES6正式將Promise對象編程了系統的一個內置對象,大家再也不用通過第三方庫開始用Promise對象了。
## Promise對象的特性
* Promise對象一旦創建,就開始執行了,你沒有辦法取消Promise對象的執行。
* Promise對象的狀態只由異步操作的結果決定,沒有任何其他的操作可以改變Promise對象的狀態如果異步操作執行成功,Promise對象將進入Resolved狀態,同時resolve函數將被調用;如果異步操作執行失敗,Promise對象將進入Rejected狀態,同時reject函數將被調用。
* Promise對象一旦進入Resolved或者Rejected狀態,狀態將不可能再發生變化,在Promise對象被銷毀之前,將一直保持Resolved或者Rejected狀態。
* 因為Promise對象的實現方法,在異步操作過程中出現的異常是不會被拋出的,因此需要在Promise對象內部進行處理(通過提供`.catch`代碼段來實現)。
下圖表示了一個Promise對象的整個生命周期。

## Promise對象的使用
創建Promise對象
--------------------------------------------------------------------------------
在ES6中,你可以通過```new Promise(function(resolve, reject){
})```來創建Promise對象,下面是一個具體的代碼。
```javascript
'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
//put some code to call
fs.readFile("temp.txt", (err, data) => {
if (err){
reject(err);
}else{
resolved(data);
}
});
});
```
關聯resolve function和reject function
--------------------------------------------------------------------------------
在上面的例子中,如果你執行這個代碼,你會發現沒有任何的效果(沒有輸出),那是因為你沒有給這個創建的Promise對象關聯相應的resolve和reject函數。下面是一個進一步的例子,這個例子將給Promise對象綁定resolve和reject函數,你就可以看到效果了。在我的測試環境中,因為我們有名為"temp.txt"的文件存在,所以輸出了```file read fail```。
```javascript
'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
//put some code to call
fs.readFile("temp.txt", (err, data) => {
if (err){
reject(err);
}else{
resolved(data);
}
});
});
promiseObj.then(function(data){
console.log("file read succ");
}, function(err){
console.log("file read fail");
})
```
在通常情況下,reject狀態的函數我們一般不在then中設置,而是在catch中設置,這樣代碼看起來更像是傳統意義上的同步代碼(和try...catch比較)。因此上面的例子可以重新寫成
```javascript
'use strict';
var fs = require("fs");
var promiseObj = new Promise(function(resolve, reject){
//put some code to call
fs.readFile("temp.txt", (err, data) => {
if (err){
reject(err);
}else{
resolved(data);
}
});
});
promiseObj.then(function(data){
console.log("file read succ");
}).catch(function(err){
console.log("file read fail");
});
```
級聯多個Promise對象
--------------------------------------------------------------------------------
Promise對象的resolve函數的參數可以是另外一個Promise對象,這樣就可以將2個Promise對象級聯起來。下面是一個簡單的例子
```javascript
'use strict';
var p1 = new Promise(function(resolve, reject){
setTimeout(() => reject(new Error("something test"), 3000));
});
var p2 = new Promise(function(resolve, reject){
setTimeout(() => resolve(p1), 1000);
});
p2.then(function(data){
console.log("p2 succ");
}).catch(function(err){
console.log("p2 fail");
});
```
## Promise的特殊函數
Promise.all()
--------------------------------------------------------------------------------
將多個Promise包裝成一個全新的Promise Object,如果所有的Promise被Resolved,那么新的Promise將被Resolve;否則新的Promise將被Reject。
Promise.race()
--------------------------------------------------------------------------------
和.all一樣,.race將把多個Promise包裝成一個新的Promise Object,不同的地方是。這些Promise之中任何一個Resolve或者Reject了,新的Promise就被Resolve或者Reject了。
Promise.resolve()
--------------------------------------------------------------------------------
將傳入的對象封裝成一個Promise對象返回。resolve方法根據以下的規則返回Promise對象。
* 如果輸入的參數本身是一個Promise對象,那么resolve方法直接返回這個對象;
* 如果輸入的對象本身有then方法(必須是一個可以接受2個function的方法),那么resolve將這個對象轉換成Promise對象,并理解調用then方法;下面是一個例子
```javascript
//for this example, you will see following output
// then function in thenobject
// Promise object resolve function is called Then function is called
var thenobject = {
then: function(resolve, reject){
console.log("then function in thenobject");
resolve("Then function is called");
}
};
var pObj = Promise.resolve(thenobject);
pObj.then(function(data){
console.log("Promise object resolve function is called " + data);
});
```
* 如果傳入的參數就是一個普通對象,那么返回的Promise對象直接處于resolved狀態,并且輸入的參數將作為resolved狀態下調用的函數的參數。下面是一個具體的例子。
```javascript
//for this example, you will see following output
// Promise object resolve function is called Hello World!
var pObj = Promise.resolve("Hello World!");
pObj.then(function(data){
console.log("Promise object resolve function is called " + data);
});
```
*如果沒有輸入參數,那么返回的Promise對象直接處于resolved狀態,并且resolved狀態下調用的函數沒有輸入參數。
done() method
--------------------------------------------------------------------------------
通過在Promise的調用鏈最后使用這個方法,可以保證catch到任何的錯誤。
finally() method
--------------------------------------------------------------------------------
如果你需要在Promise結束的時候(不管resolve還是reject結束),都有一個函數被調用,那么就需要使用這個方法。這個方法接受一個回調函數作為輸入。當Promise結束的時候,這個回調函數將被調用。
- Introduction
- Nodejs 4.x新特性
- classes
- typed arrays
- generators
- collections
- Set
- Map
- arrow functions
- block scoping
- template strings
- promises
- symbols
- Koa基礎
- 上下文
- koa-generator
- 安裝
- 創建項目
- 更改視圖模板引擎
- Routes
- HTTP
- Get
- 如何獲取query參數
- 如何獲取params
- Post
- 從post獲取參數
- 標準表單(Post with x-www-form-urlencoded)
- 文件上傳(Post with form-data)
- Post with raw
- 數據庫
- MySQL
- Mongo
- 流程控制
- generator/co
- es6的generator是什么?
- co = generator + promise
- async/await
- promise with bluebird
- 測試
- Mocha
- Supertest
- 部署
- 最佳實踐
- FAQ
- 如何發布本書到git pages
- 如何知道require模塊的用法
- koa中的異常處理