目錄 (づ ̄ 3 ̄)づ=>
[TOC]
## 序
Promise是js、NodeJS中很重要的一個知識點,牽連縱廣。
本文會先提出一個promise里的用法,然后再對這個用法進行實現,以此來加深對promise用法的印象并理解之。
## 什么是Promise
Promise翻譯成中文是承諾、許諾的意思,這代表著它不是立即被執行,而是在未來的某個時間點才會被一錘定音(不再能更改結果)。一個承諾,在未來可能被實現也可能實現不了,這體現在Promise的設計里就是你可以通過`new Promise`的時候傳入一個`executor`執行函數作為參數來決定什么樣的情況承諾會被兌現什么樣的情況會實現不了。
**Promise不是一個像ajax一樣的異步方法,它是幫助我們優化一個異步方法書寫形式的方法,以便于異步代碼更易書寫和維護。**
## 什么時候需要一個Promise
在NodeJS中的讀寫操作往往是異步的,我們不能立即拿到數據,這意味著那些必須拿到數據后才能做的操作不能放在當前隊列中,必須先存起來,當數據拿到后再調用這些操作方法。我們拿到數據后的操作方法有兩大類:
- 以拿到的數據為依據去進行下一個異步操作
- 和另外一個異步操作的結果進行拼接后同步輸出
對于第一類情況,容易產生一種被稱為`回調地獄` 的書寫形式
```
$.ajax('url',success(){
...
$.ajax('url',success(){
...
$.ajax('url',success(){
...
})
})
})
```
對于第二類情況,因為都是異步操作,我們不能像常規操作數據拼接一樣操作異步方法得來的數據,我們需要實現一個方法來搭橋。
而Promise幫我們完美解決了以上的問題,它就是因此而產生的。
## Promise使用前后對比:只存在一個異步方法時
以下為只使用一次異步操作時的書寫對比
```
//使用promise前
readFile('1.txt','utf8',(err,data)=>{
if(err){
//do something
}
if(data){
//do something
}
});
//-----------------------------------------
//使用promise后
let p1 = new Promise((resolve,reject)=>{
readFile('1.txt','utf8',(data,err)=>{
if(err) return reject(err); //讀取失敗,將err交給promise,并將promise的狀態修改為rejected
resolve(data); //讀取成功,將data交給promise,并將promise的狀態修改為fulfilled
})
});
p1.then((data)=>{ //data為resolve()時傳遞的參數
//讀取成功時執行的回調
},(err)=>{ //err為reject()時傳遞的參數
//讀取失敗時執行的回調
})
```
如果只是一次異步操作而不進行嵌套時,似乎原生的異步操作方式更加簡潔。。。嗯。。。的確是的,但如果進行多次嵌套的話就不一樣了,另外我們還可以在promise的基礎之上和生成器結合,使原生異步操作像操作同步方法一樣操作異步方法,不過這是后話了。。。總之,promise是基礎。。。嗯。。
我們先來實現這段達到代碼效果的promise
### Promise實現
**重點是把異步的回調函數先存起來,當promise狀態改變時再執行對應的回調函數。**
```
const PENDING = 'pending'; //初始態
const FULFILLED = 'fulfilled'; //順利執行
const REJECTED = 'rejected'; //失敗
function Promise(executor) {
let self = this; //先緩存當前promise實例
self.status = PENDING; //設置狀態
self.onResolvedCallBacks = []; //定義存放成功的回調的數組
self.onRejectedCallBacks = []; //定義存放失敗的回調數組
self.value = undefined;
//2.1
function resolve(value) { //2.1.1
if (self.status == PENDING) {
self.status == FULFILLED;
self.value = value; //成功后會得到一個值,這個值不能改
self.onResolvedCallBacks.forEach(cb => cb(self.value)); //調用所有成功的回調
}
}
function reject(reason) {
if (self.status == PENDING) { //2.1.2
self.status == REJECTED;
self.reason = reason;
self.onRejectedCallBacks.forEach(cb => cb(self.reason));
}
}
try {
executor(resolve, reject); //因為此函數執行時可能會異常,所以需要捕獲,如果出錯了,需要用錯誤對象reject
} catch (e) {
reject(e);
}
}
//onFulfilled 是用來接收promise成功的值或者失敗的原因
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this;
if (self.status == FULFILLED) { //如果當前promise狀態已經是成功態了,onFulfilled直接取值
onFulfilled(self.value);
}
if (self.status == REJECTED) { //如果當前promise狀態已經是成功態了,onFulfilled直接取值
onRejected(self.value);
}
if (self.status == PENDING) {
self.onResolvedCallBacks.push(function () {
onFulfilled(self.value);
});
self.onRejectedCallBacks.push(function () {
onRejected(self.value);
});
}
};
module.exports = Promise;
```
## Promise使用前后對比:異步方法嵌套時
讀取三個文件,第二個文件的文件名為第一個文件的內容,第三個為第二個文件的內容。
```
//使用promise前
readFile('1.txt','utf8',(err,data)=>{
if(err){
//do something
}
if(data){
readFile(data'utf8',(err,data)=>{
if(err){
//do something
}
if(data){
//do something
//... 再來一層
readFile(data'utf8',(err,data)=>{
if(err){
//do something
}
if(data){
//do something
}
});
}
});
}
});
//-----------------------------------------
//使用promise后
read('1.txt','utf8').then((data)=>{ //data為resolve()時傳遞的參數
//讀取成功時執行的回調
return read(data,'utf8'); //讀取第二個文件
).then((data)=>{
return read(data,'utf8'); //讀取第三個文件
}).then((data)=>{
console.log(data); //輸出第三個文件
}).catch((err)=>{
//如果中間出錯了就捕獲err做點什么...
})
let read = function(url,encode){
return new Promise((resolve,reject)=>{
readFile(url,encode,(data,err)=>{
if(err) return reject(err); //讀取失敗,將err交給promise,并將promise的狀態修改為rejected
resolve(data); //讀取成功,將data交給promise,并將promise的狀態修改為fulfilled
})
});
}
```
我們可以發現,這里我們通過類似jQ鏈式操作一樣的方式連續then了三次,**每一次then中的成功回調函數中的參數為這次return的promise的結果(value or reason)**。除此之外若只是return一個普通的值,那么下一次then的成功回調函數中的參數即為這個值。
>[danger] 另外請注意只有在失敗回調中拋出一個異常,下一次的then才會走失敗回調。而如果只是在失敗回調中return,那么下一次則不會走失敗回調。
### Promise實現
我們之所以能連續then,是因為**then方法**返回了了promise對象,類似jQ,但這個promise對象是一個新的promise對象。
而這個`新的promise對象的狀態`是依據上一個promise的回調函數來決定的,如果上一次的回調函數中的沒有拋出異常,那么這個新的promise對象的狀態則會為 `fulfilled`。
另外新對象的回調函數中的參數為上一個promise的結果(value/reason)。
```
Promise.prototype.then = function (onFulfilled, onRejected) {
let self = this
, promise2;
if (self.status == FULFILLED) {
return promise2 = new Promise((resolve, reject) => { //調用自己創建一個新的Promise對象
setTimeout(()=>{
try { //2.2.7.2
let x = onFulfilled(self.value); //鏈式調用時將上一個的promise的結果當做下一次then成功時候的參數傳入
resolvePromise(promise2, x, resolve, reject); //2.2.7.1
} catch (e) { //回調函數中拋出了異常
reject(e); //新的promise狀態轉換為rejected
}
});
});
}
else if (self.status === REJECTED) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
});
}
else { //pending狀態 先將回調函數都儲存起來
return promise2 = new Promise((resolve,reject)=>{
self.onFulfilledCallbacks.push(function(){
try{
let x =onFulfilled(self.value);
//如果獲取到了返回值x,會走解析promise的過程
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
self.onRejectedCallbacks.push(function(){
try{
let x =onRejected(self.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
});
});
}
```
其中`resolvePromise` 的作用為,轉換新promise對象的狀態并確定其結果的值(value/reason)。
1.resolvePromise(promise2, x, resolve, reject);
2.resolvePromise(promise2, y, resolve, reject); //return promise
3.value.then(resolve,reject); //resolve(promise)
4.onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected == 'function' ? onRejected : reason => {
throw reason
};
5.setTimeout
6.throw err
promise特性