# 什么是Promise
Promise是異步編程的一種解決方案,比傳統的解決方案-回調函數和事件等-更加合理且更加強大。它最早由社區提出并實現,ES6將其寫進了語言標準,統一了用法,并原生提供了Promise對象。
所謂Promise,撿來來說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上來說,Promise是一個對象,從他可以獲取一步操作的消息。Promise提供統一的API,各種異步操作都可以用同樣的方法進行處理。
# 幾種常見異步編程方案
* 回調函數
* 事件監聽
* 發布/訂閱
* Promise對象
## 拿回調函數說說事
1. 對于回調函數我們用Jquery的ajax獲取數據時都是以回調函數方式獲取的數據
```
$.get(url, data => {
console.log(data)
)
```
2. 如果說當我們需要發送多個異步請求并且每個請求之間需要相互依賴 那這時我們只能以嵌套方式來解決形成 **"回調地獄"**
```
$.get(url, data1 => {
console.log(data1)
$.get(data1.url, data2 => {
console.log(data2)
...
})
})
```
這樣一來,在處理越多的異步邏輯時,就需要越深的回調嵌套,這種編碼模式的問題主要有以下幾個:
* 代碼邏輯書寫順序與執行順序不一致,不利于閱讀與維護
* 異步操作的順序變更時,需要大規模的代碼重構
* 回調函數基本都是匿名函數,bug 追蹤困難
* 回調函數是被第三方庫代碼(如上例中的 ajax )而非自己的業務代碼所調用的,造成了 IoC 控制反轉
## Promise 處理多個相互關聯的異步請求
1. Promise可以更直觀的方式來解決 "回調地獄"
```
const request = url => new Promise((resolve, reject) => {
$.get(url, data => {
resolve(data)
})
})
// 請求data1
request(url).then(data1 => {
return request(data1.url);
}).then(data2 => {
return request(data2.url)
}).then(data3 => {
console.log(data3)
}).catch(err => throw new Error(err))
```
2. 相信大家在 vue/react 都是用axios fetch 請求數據也都支持Promise API
```
import axios from 'axios';
axios.get(url).then(data => {
console.log(data)
})
```
# 狀態
Promise對象只有三種狀態:
* pending(未完成/等待中/進行中)- 表示還沒有得到結果
* resolved(也稱fulfilled)(已完成/已成功)- 在異步操作成功時調用,并將結果作為參數傳遞出去。
* rejected(失敗/已失敗)- 在異步操作失敗時調用,并將報出的錯誤作為參數傳遞出去
注意promsie狀態 只能由 pending => fulfilled/rejected, 一旦修改就不能再變
Promise對象有以下兩個特點:
1. 對象的狀態不受外界影響
Promise對象代表一個異步操作,只有異步操作的結果可以決定當前是哪種狀態,其他任何操作都無法改變這個狀態,這也是Promise名字的由來。所謂的“君子一言,駟馬難追”,承諾將來某個狀態,且該狀態不會被其他因素改變。
2. 一旦狀態改變就不會再變,任何時候都可以得到這個結果
Promise對象的狀態改變只有兩種可能(promise對象初始化狀態為pending):
* 當調用resolve(成功),pending => fulfilled
* 當調用reject(失敗),pending => rejected
只要這兩種情況發生,狀態就凝固了,不會再變,而是一直保持這種結果,這是就稱為Resolved(已定型)。就算改變已經發生,在對Promise對象添加回調函數,也會立即得到這個結果。這與事件(Event)完全不同。事件的特點是,如果錯過了它,再去監聽是得不到結果的。
# 基本用法
```
let promise = new Promise((resolve, reject) => {
// do something
if (/* 異步執行成功 */) {
resolve('請求成功')
} else {
reject('請求失敗')
}
})
promise.then(value => {
console.log('resolve', value)
}, error => {
console.log('reject', error)
})
// 或者
promise.then(value => {
console.log('resolve', value)
}).catch(error => {
console.log('reject', error)
})
```
>[info]Tip: 從基本用法的例子中我們看到Promise構造函數的參數是resolve和reject,并不是三種狀態中的fulfilled和rejected,原因就是:resolved表示的是已結束(已定型),它包含fullfilled和rejected兩種狀態,但使用中,我們默認的將resolved當做fulfilled(成功)使用。
# 基本實現
首先能看出Promise是一個類或者說是一個構造器,構造器接收一個函數參數
```
function Promise(executor) {
}
```
Promise實例至少擁有then()和catch()方法
```
Promise.prototype = {
then: function() {
},
catch: function() {
}
}
```
構造函數內部回調構造函數傳遞過來的參數時,需要回傳兩個參數,分別是resolve和reject,而這兩個參數各自都又是一個函數,所以構造函數內部需要有這樣兩個函數,以供回調參數時回傳回去。所以修改Promise如下:
```
function Promise(executor) {
// 定義resolve函數
function resolve() {
}
// 定義reject函數
function reject() {
}
// 回調,并且回傳resolve和reject
executor(resolve, reject)
}
```
觀察下面代碼片段:
```
if (/* 異步執行成功 */) {
resolve('請求成功')
} else {
reject('請求失敗')
}
```
可以得出,在調用resolve()和reject()時需要傳入參數,所以這兩個方法還需要參數
```
function Promise(executor) {
/**
* 成功回調函數
* @param value 回調參數
*/
function resolve(value) {
}
/**
* 失敗回調函數
* @param reason 回調參數
*/
function reject(reason) {
}
// 回調,并且回傳resolve和reject
executor(resolve, reject)
}
```
觀察一下代碼片段:
```
promise.then(value => {
console.log('resolve', value) // resolve 請求成功
}, error => {
console.log('reject', error) // reject 請求失敗
})
```
得出then()方法接收兩個函數,作為參數。
第一個函數參數為resolved后被調用的參數,并且需要回傳結果值
第二個函數參數為rejected后被調用的參數,并且需要回傳結果值,這個參數可以省略
于是我們修改上面的then()方法
```
Promise.prototype = {
then: function(onResolved, onRejected) {
}
}
```
觀察一下代碼片段:
```
if (/* 異步執行成功 */) {
resolve('請求成功')
} else {
reject('請求失敗')
}
```
```
promise.then(value => {
console.log('resolve', value) // resolve 請求成功
}, error => {
console.log('reject', error) // reject 請求失敗
})
```
可以看出,構造`Promise`時,函數內部根據異步執行結果確定調用`resolve`還是`reject`,這個毋庸置疑。現在的問題在于,調用了`resolve`之后`then()`方法的第一個參數如何被調用?失敗之后`then()`方法的第二個參數又如何被調用?
答案是:我們可以把`then(onResolved, onRejected)`方法的兩個函數參數`onResolved`和`onRejected`當做`Promise`的實例屬性暫存起來,然后再構造函數內部的`resolve()`方法和`reject()`方法被調用時,調用這兩個暫存起來的函數即可實現
```
function Promise(executor) {
var self = this
self.onResolved = null // 保存onResolved
self.onRejected = null // 保存onRejected
/**
* 成功回調函數
* @param value 回調參數
*/
function resolve(value) {
self.onResolved(value) // 調用then()方法的第一個函數參數
}
/**
* 失敗回調函數
* @param reason 回調參數
*/
function reject(reason) {
self.onRejected(reason) // 調用then()方法的第二個函數參數
}
// 回調,并且回傳resolve和reject
executor(resolve, reject)
}
Promise.prototype = {
then: function(onResolved, onRejected) {
this.onResolved = onResolved
this.onRejected = onRejected
}
}
```
到此我們實現了一個簡單到不能再簡單的Promise,趕緊擼段代碼測試一把。
使用setTimeout模擬異步,通過傳入的isSuccess參數值模擬異步成功與否
```
var promise = isSuccess => new Promise((resolve, reject) => {
setTimeout(() => {
if (isSuccess) {
resolve('請求成功了')
} else {
reject('請求失敗了')
}
}, 1000)
})
```
傳入true,等待1s打印成功;傳入false,等待1s打印失敗
```
promise(true).then(value => {
console.log('成功', value)
}, error => {
console.log('失敗', error)
})
promise(false).then(value => {
console.log('成功', value)
}, error => {
console.log('失敗', error)
})
```
下面我們整體看一下實現的代碼,我去掉了所有的注釋,只保留代碼部分
```
function Promise(executor) {
var self = this
self.onResolved = null
self.onRejected = null
function resolve(value) {
self.onResolved(value)
}
function reject(reason) {
self.onRejected(reason)
}
executor(resolve, reject)
}
Promise.prototype = {
then: function(onResolved, onRejected) {
this.onResolved = onResolved
this.onRejected = onRejected
}
}
```