# Promise/a+規范
順序執行的代碼和錯誤有限的回調方式都是js引擎默認支持的,這部分大家會調用接口,無太多變化,而Promise是對callback的思考,或者說改良方案,目前使用非常普遍,這里詳細講解一下。
## Node.js里的Promise
promise最早是在commonjs社區提出來的,當時提出了很多規范。比較接受的是promise/A規范。后來人們在這個基礎上。提出了promise/A+規范,也就是實際上的業內推行的規范。es6也是采用的這種規范。
> The Promise object is used for asynchronous computations. A Promise represents an operation that hasn't completed yet, but is expected in the future.
Promise對象用于異步技術中。Promise意味著一個還沒有完成的操作(許愿),但在未來會完成的(實現)。

在Node.js 0.12里實現9/11,在6.2和7實現100%,中間版本實現了10/11。所以Node.js對Promise的支持是非常好的,0.12之后的絕大部分版本都支持的不錯。
Promise 的最大優勢是標準,各類異步工具庫都認同,未來的 async/await 也基于它,用它封裝 API 通用性強,用起來簡單。
要想知道node.js有哪些比較好的promise實現,最好的辦法就是看一下最知名的bluebird庫的[benchmark](http://bluebirdjs.com/docs/benchmarks.html)里比較里哪些。
* async@1.5.0
* babel@5.8.29
* davy@1.0.1
* deferred@0.7.3
* kew@0.7.0
* lie@3.0.1
* neo-async@1.6.0
* optimist@0.6.1
* promise@7.0.4
* q@1.4.1
* rsvp@3.1.0
* streamline@1.0.7
* text-table@0.2.0
* vow@0.4.11
* when@3.7.4
## Promise是什么?
> A promise is an abstraction for asynchronous programming. It’s an object that proxies for the return value or the exception thrown by a function that has to do some asynchronous processing. — Kris Kowal on JSJ
Promise表示一個異步操作的最終結果。與Promise最主要的交互方法是通過將函數傳入它的then方法從而獲取得Promise最終的值或Promise最終最拒絕(reject)的原因。
* 遞歸,每個異步操作返回的都是promise對象
* 狀態機:三種狀態轉換,只在promise對象內部可以控制,外部不能改變狀態
* 全局異常處理
定義
~~~
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
~~~
## 術語
* Promises Promise規范自身
* promise對象 promise對象指的是 Promise 實例對象
* ES6 Promises 如果想明確表示使用 ECMAScript 6th Edition 的話,可以使用ES6作為前綴(prefix)
* Promises/A+ Promises/A+。 這是ES6 Promises的前身,是一個社區規范,它和 ES6 Promises 有很多共通的內容。
* Thenable 類Promise對象。 擁有名為.then方法的對象。
## hello promise
給出一個最簡單的讀寫文件的api實例,它是error-first風格的典型api
async/promise/hello.js
~~~
// callbacks
var fs = require("fs");
fs.readFile('./package.json', (err, data) => {
if (err) throw err;
console.log(data.toString());
});
~~~
下面,我們把它變成promise的簡單示例
async/promise/hellopromise.js
~~~
// callbacks to promise
var fs = require("fs");
function hello (file) {
return new Promise(function(resolve, reject){
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.toString())
}
});
});
}
hello('./package.json').then(function(data){
console.log('promise result = ' + data)
}).catch(function(err) {
console.log(err)
})
~~~
這二段代碼執行效果是一模一樣的,唯一的差別是前一種寫法是Node.js默認api寫法,以回調為主,而后一種寫法,通過返回promise對象,在fs.readFile的回調函數,將結果延后處理。
這就是最簡單的promise實現
形式
~~~
new Promise(function(resolve, reject){
})
~~~
參數
* resolve 解決,進入到下一個流程
* reject 拒絕,跳轉到捕獲異常流程
調用
~~~
hello('./package.json').then(function(data){
})
~~~
全局處理異常
~~~
hello('./package.json').then(function(data){
}).catch(function(err) {
})
~~~
結論
> Promise核心:將callback里的結果延后到then函數里處理或交給全局異常處理
## 封裝api的過程
還是以上面的fs.readFile為例
~~~
fs.readFile('./package.json', (err, data) => {
if (err) throw err;
console.log(data.toString());
});
~~~
參數處理:除了callback外,其他東西都放到新的函數的參數里
~~~
function hello (file) {
...
}
~~~
返回值處理:返回Promise實例對象
~~~
function hello (file) {
return new Promise(function(resolve, reject){
...
});
}
~~~
結果處理:通過resolve和reject重塑流程
~~~
function hello (file) {
return new Promise(function(resolve, reject){
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.toString())
}
});
});
}
~~~
我們知道所有的Node.js都是error-first的callback形式,通過上面的例子,我們可以肯定是所有的Node.js的API都可以這樣來處理,只要它們遵守Promise規范即可。
## 每個函數的返回值都是Promise對象
為了簡化編程復雜性,每個函數的返回值都是Promise對象,這樣的約定可以大大的簡化編程的復雜。
它可以理解為是遞歸的變種思想應用,只要是Promise對象,就可以控制狀態,就可以支持then方法,參數還是Promise對象,這樣就可以無限個Promise對象鏈接在一起。
~~~
// callbacks to promise
var fs = require("fs");
function hello (file) {
return new Promise(function(resolve, reject){
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.toString())
}
});
});
}
function world (file) {
return new Promise(function(resolve, reject){
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.toString())
}
});
});
}
function log(data){
return new Promise(function(resolve, reject){
console.log('promise result = ' + data)
resolve(data)
});
}
hello('./package.json').then(log).then(function(){
return world('./each.js').then(log)
}).catch(function(err) {
console.log(err)
})
~~~
這里可以看出
* `hello`、`world`、`log`?返回單個Promise對象
* `hello('./each.js').then(log)`?返回流程鏈
無論是單個,還是流程鏈的返回值都是Promise對象,那么它就是一樣的。
## 鏈式的thenable
每個promose對象都有then方法,也就是說,then方法是定義在原型對象Promise.prototype上的。它的作用是為Promise實例添加狀態改變時的回調函數。
一般實現,類似于
~~~
Promise.prototype.then = function(sucess, fail) {
this.done(sucess);
this.fail(fail);
return this;
};
~~~
它的返回值是this,這就是為什么then可以鏈式操作的原因。
then的2個參數
* sucess是fulfilled狀態的回調函數
* fail是rejected狀態的回調函數
一般都是穿sucess回調函數即可。
## 狀態轉換
一個Promise必須處在其中之一的狀態:pending, fulfilled 或 rejected.
* pending: 初始狀態, 非 fulfilled 或 rejected.
* fulfilled: 完成(成功)的操作.
* rejected: 拒絕(失敗)的操作.
這里從pending狀態可以切換到fulfill狀態,也可以從pengding切換到reject狀態,這個狀態切換不可逆,且fulfilled和reject兩個狀態之間是不能互相切換的。
一定要注意的是,只有異步操作的結果,才可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
Promise對象可以理解為一個樂高積木,它對下一個流程,傳送狀態和具體結果。

如果是pending狀態,則promise:
* 可以轉換到fulfilled或rejected狀態。
如果是fulfilled狀態,則promise:
* 不能轉換成任何其它狀態。
* 必須有一個值,且這個值不能被改變。
如果是rejected狀態,則promise可以:
* 不能轉換成任何其它狀態。
* 必須有一個原因,且這個值不能被改變。
”值不能被改變”指的是其identity不能被改變,而不是指其成員內容不能被改變。
## reject和resove流程再造
前面講了,每個函數的返回值都是Promise對象,每個Promise對象都有then方法,這是它可以遞歸思路的解決辦法。
那么問題來了,如何在連續的操作步驟里,完成流程再造呢?這其實才是異步流程控制最核心的問題。
我們知道Promise的使用形式如下:
~~~
new Promise(function(resolve, reject){
})
~~~
下面仍然使用fs的例子,見reflow.js
`way 1`:簡單模式
~~~
hello('./package.json').then(function(data){
console.log('way 1:\n')
return new Promise(function(resolve, reject){
console.log('promise result = ' + data)
resolve(data)
});
}).then(function(data){
return new Promise(function(resolve, reject){
resolve('1')
});
}).then(function(data){
console.log(data)
return new Promise(function(resolve, reject){
reject(new Error('reject with custom err'))
});
}).catch(function(err) {
console.log(err)
})
~~~
這是一個常規的例子,就是在then里面的promise對象里,通過resolve將流程進行到下一步,在reject的時候拋出異常。這里面的每一個promise對象里都可以這樣做,那么是不是這個操作流程就是可控的了?
`way 2`:嵌套模式
~~~
hello('./package.json').then(function(data){
console.log('\n\nway 2:\n')
return new Promise(function(resolve, reject){
console.log('promise result = ' + data)
resolve(data)
}).then(function(data){
return new Promise(function(resolve, reject){
resolve('1')
});
}).catch(function(err) {
console.log(err)
})
}).then(function(data){
console.log(data)
return new Promise(function(resolve, reject){
reject(new Error('reject with custom err'))
});
}).catch(function(err) {
console.log(err)
})
~~~
這里的做法是,把第一個then和第二個then合并到一個流程里。這樣做的好處是,這個流程也可以考慮單獨處理異常。為了某些粒度更新的異步處理,是非常有好處的。
`way 3`:嵌套模式的refact清晰版
~~~
var step1 = function(data){
console.log('\n\nway 3:\n')
return new Promise(function(resolve, reject){
console.log('promise result = ' + data)
resolve(data)
}).then(function(data){
return new Promise(function(resolve, reject){
resolve('1')
});
}).catch(function(err) {
console.log(err)
})
}
var step2 = function(data){
console.log(data)
return new Promise(function(resolve, reject){
reject(new Error('reject with custom err'))
});
}
hello('./package.json').then(step1).then(step2).catch(function(err) {
console.log(err)
})
~~~
把每個獨立的操作抽成函數,然后函數的返回值是Promise對象,這樣就可以在真正的流程鏈里隨意組織了。
它們就好比是積木一樣,可以讓邏輯更清楚,讓代碼更具可讀性和可維護性。如果再極端點,每個操作都放到獨立文件里,變成模塊,是不是更爽呢?
`way 4`:final版,把每個獨立的操作放到獨立文件里,變成模塊
原理: 使用`require-directory`
根據commonjs規范,require只能引用某一個文件,當一個文件夾里有很多文件,每一個都去require是很麻煩的,`require-directory`就是一個便捷模塊,可以把某個文件夾內的多個文件掛載到一個對象。
原理,遞歸遍歷文件,讀取具體文件,如果是遵循commonjs規范的模塊,就掛載在它的返回值對象上。
比如reflow/tasks/index.js
~~~
var requireDirectory = require('require-directory');
module.exports = requireDirectory(module);
~~~
這樣`reflow/tasks`下的所有遵循commonjs規范的模塊都可以掛載
reflow/tasks/hello.js
~~~
var fs = require("fs");
module.exports = function hello (file) {
return new Promise(function(resolve, reject){
fs.readFile(file, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.toString())
}
});
});
}
~~~
這其實和之前的定義是一模一樣的,唯一差別就是變成了模塊,使用了module.exports來導出。
其他的step1和step2以此類推,下面我們卡一下具體調用的代碼
~~~
var tasks = require('./tasks')
tasks.hello('./package.json').then(tasks.step1).then(tasks.step2).catch(function(err) {
console.log(err)
})
~~~
給出具體的流程圖

首先`require('./tasks')`獲得tasks目錄下的所有操作任務定義,然后在下面的Promise流程里處理,可以看出定義和實現分離,讓代碼有更好的可讀性。
如果,這時我們恰好需要調整step1和step2的順序,是不是非常的簡單?
~~~
var tasks = require('./tasks')
tasks.hello('./package.json').then(tasks.step2).then(tasks.step1).catch(function(err) {
console.log(err)
})
~~~
更多好處,自行體會吧,這里就不做更多解釋了。
## 錯誤處理
常用的處理方式是全局處理,即所有的異步操作都由一個catch來處理
~~~
promise.then(function(result) {
console.log('Got data!', result);
}).catch(function(error) {
console.log('Error occurred!', error);
});
~~~
當然,then方法的第二個參數也是可以的
~~~
promise.then(function(result) {
console.log('Got data!', result);
}).then(undefined, function(error) {
console.log('Error occurred!', error);
});
~~~
如果有多個then配對的reject函數呢?是不是可以更加靈活?這其實就要取決于你的業務復雜程度里。
錯誤處理最簡單的辦法是在promise里使用try/catch的語句。在try/catch塊中,它可能去捕獲異常,并顯示處理它:(TODO: 重寫個更簡單例子)
~~~
try {
throw new Error('never will know this happened')
} catch (e) {}
~~~
在promises里可以這樣寫
~~~
readFile()
.then(function (data) {
throw new Error('never will know this happened')
})
~~~
為了打印errors,這里以簡單的.then(null, onRejected)語句為例
~~~
readFile()
.then(function (data) {
throw new Error('now I know this happened')
})
.then(null, console.error)
~~~
類庫包括一些暴露error的其他選項。比如Q就提供了done方法,可以再次跑出error異常的。
鏈式寫法很方便,可以隨意組合,
api/catch.js
~~~
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "Success!"
return Promise.reject('oh, no!');
}).catch(function(e) {
console.log(e); // "oh, no!"
// return Promise.reject('oh, no! 2');
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
~~~
執行
~~~
$ node api/catch.js
Success
oh, no!
after a catch the chain is restored
~~~
api/catch2.js
~~~
var p1 = new Promise(function(resolve, reject) {
resolve('Success');
});
p1.then(function(value) {
console.log(value); // "Success!"
return Promise.reject('oh, no!');
}).catch(function(e) {
console.log(e); // "oh, no!"
return Promise.reject('oh, no! 2');
}).then(function(){
console.log('after a catch the chain is restored');
}, function () {
console.log('Not fired due to the catch');
});
~~~
執行
~~~
$ node api/catch2.js
Success
oh, no!
Not fired due to the catch
~~~
## Promise/a+只有Node.js有么?
這是大家經常問到的問題,明確的說,promise/a+是規范。
而Node.js因為完全異步,導致callbackhell,所以是所有語言里比較早引入Promise/a+規范的,是Promise/a+規范的經典實現。
其他語言也有類似實現的,比如oc,java,ruby等
[https://promisesaplus.com/implementations](https://promisesaplus.com/implementations)

- 前言
- 1 skill
- 1.1 Coding WebIDE
- 1.2 git
- 1.3 extra practice
- 1.4 預習
- 2 nodejs入門
- 2.1 入門
- 2.2 安裝
- 2.3 helloworld
- 2.4 commonJS規范
- 2.5 模塊導出
- 2.6 Nodejs代碼調試
- 2.7 編寫Nodejs模塊
- 2.8 最小化問題
- 2.9 隨堂練習
- 3 異步流程控制
- 3.1 什么時候會用到異步流程控制
- 3.2 簡單做法async模塊
- 3.3 Promise/a+規范
- 3.4 Node.js Promise/a+實現
- 3.5 生成器Generators/yield
- 3.6 Async函數/Await
- 3.7 神奇的co
- 3.8 5種 yieldable
- 3.9 學習重點
- 3.10 隨堂練習
- 4 express和微信開發入門
- 4.1 入門
- 4.2 connect
- 4.3 靜態Http服務器
- 4.4 那些預處理器
- 4.5 路由
- 4.6 視圖與模塊引擎
- 4.7 中間件
- 4.8 更多實踐
- 4.9 微信入門
- 4.10 隨堂練習:完成登錄、注冊功能
- 5 微信實例與H5實踐
- 5.1 微信基礎和sandbox
- 5.2 公眾號菜單和自動回復
- 5.3 微信OAuth用戶授權
- 5.4 微信分享
- 5.5 wechat-api
- 5.6 H5-上篇
- 5.7 H5-下篇
- 5.8 隨堂練習
- 6 weui實戰
- 6.1 使用bower
- 6.2 移動端抽象
- 6.3 優化滑動列表
- 6.4 weui
- 6.5 讓weui和iscroll結婚
- 6.6 優化事件
- 6.7 how-to-write-h5
- 6.8 優化無止境
- 6.9 隨堂練習
- 7 微信支付
- 7.1 吹個牛
- 7.2 支付概述
- 7.3 科普幾個概念
- 7.4 準備
- 7.5 調試
- 7.6 公眾號支付(JSAPI)
- 7.7 對賬單
- 7.8 數據處理
- 7.9 隨堂練習
- 8 項目實戰《付費課程系統MVP》
- 8.1 需求分析
- 8.2 ui/ue
- 8.3 技術棧
- 8.4 模型
- 8.5 靜態api
- 8.6 開發
- 8.7 部署
- 8.8 監控
- 8.9 數據統計
- 8.10 demo
- 9 高級篇
- 9.1 前后端分離實踐?
- 9.2 如何展望未來的大前端
- 9.3 容器和微服務
- 10 答疑問題收集