[TOC]
說到 ES6,Promise 是繞不過的問題;如果說 ES6 的 Class 是基於 Javascript 原型繼承的封裝
,那麼 Promise 則是對 callback 回調機制的改進
。這篇文章,不談 Promise 的實際應用;聊一下 Promise 的實現原理,從最簡單的解決方案入手,一步一步的自己實現一個 SimplePromise。
正文
從最簡單的 Promise 初始化和使用入手:
const pro = new Promise ((res, rej) => {})
pro.then(data => {}, err => {})
Promise 的構造函數如上,需要傳遞一個函數作爲參數,這個函數有兩個變量: resolve, reject。而 Promise 有不同的執行狀態,分三種情況:Resolve, Reject, Pending。根據以上的信息,寫出最基本的 SimplePromise 的類結構:
class SimplePromise{
constructor(handler){
this._status = "PENDING"
handler(this._resolve.bind(this), this._reject.bind(this))//參數函數的作用域指向Class
}
_resolve(){}
_reject(){}
}
接下來思考一下_resolve
與_reject
兩個函數的作用。我們知道,Promise 根據 then 方法來執行回調,而 then 是根據狀態
判斷要執行的回調函數。不難推導出,_resolve
與_reject
正是根據handler
的執行來進行狀態變更的,而狀態只能由Pending
向Reslove
或Rejected
轉換,所以有:
class SimplePromise{
constructor(handler){
...
}
_resolve(val){//異步返回的數據
if(this._status === "PENDING"){//保證狀態的不可逆性
this._status = "RESOLVED"
this._value = val
}
}
_reject(val){
if(this._status === "PENDING"){
this._status = "REJECTED"
this._value = val
}
}
}
then的調用邏輯
下面分析 then 函數的邏輯,從調用入手:
pro.then(data => {}, err => {})
then 接收兩個參數,第一個是執行成功調用的函數,第二個是執行失敗調用的函數。
class SimplePromise{
constructor(handler){
...
}
_resolve(val){
...
}
_reject(val){
...
}
then(success, fail){
switch (this._status){
case "PENDING":
break;
case "RESOLVED":
success(this._value)
break;
case "REJECTED":
fail(this._value)
break;
}
}
}
以上實現了最簡單的一個 Promise
測試代碼:
const pro = new SimplePromise(function(res, rej) {
let random = Math.random() * 10
if(random > 5){
res("success")
}
else{
rej("fail")
}
})
pro.then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})
當然,這不能算是一個 Promise,目前僅僅實現了根據狀態調用不同的回調函數。還沒有實現異步。
那如何實現異步呢?關鍵在於 then 函數,當判斷_status
爲PENDING
時,如何延後調用 success
與fail
函數,等待狀態改變後再調用?
支持異步
這裏採用數組來存儲 fail 與 success 函數:
class SimplePromise{
constructor(handler){
this.status = "PENDING"
this._onSuccess = []//存儲fail 與 success 函數
this._onFail = []
handler(this._resolve.bind(this), this._reject.bind(this))
}
_resolve(val){
if(this.status === "PENDING"){
...
let temp
while(this._onSuccess.length > 0){//依次執行onSuccess中的回調函數
temp = this._onSuccess.shift()
temp(val)
}
}
}
_reject(val){
if(this.status === "PENDING"){
...
let temp
while(this._onFail.length > 0){
temp = this._onFail.shift()
temp(val)
}
}
}
then (success, fail){
switch (this.status){
case "PENDING":
this._onSuccess.push(success)
this._onFail.push(fail)
break;
...
}
}
}
使用 onSuccess
和 onFail
來存儲回調函數,當處理狀態爲 PENDING
時,將回調函數 push
到相應的數組裏,當狀態變更後,依次執行數組裏的回調函數。
測試代碼:
const pro = new SimplePromise(function(res, rej) {
setTimeout(function(){
let random = Math.random() * 10
if(random > 5){
res("success")
}
else{
rej("fail")
}
}, 2000)
})
pro.then(function(data) {
console.log(data)
}, function(err) {
console.log(err)
})
兩秒後,會執行相應的回調。
到目前爲止,最最最簡單的一個 Promise 骨架已經基本完成了。但是還有很多功能待完成。現在可以稍微休息一下,喝個咖啡打個雞血,回來我們會繼續讓這個 Promise 骨架更加豐滿起來。
. . . . . .
完善Promise
歡迎回來,下面我們繼續完善我們的 Promise。
上面完成了一個最基礎的 Promise,然而還遠遠不夠。首先,Promise 需要實現鏈式調用,其次 Promise 還需要實現 all race resolve reject
等靜態函數。
首先,如何實現 then 的鏈式調用呢?需要 then 返回的也是一個 Promise。
於是有
class SimplePromise{
...
then(success, fail){
return new SimplePromise((nextSuccess, nextFail) => {
const onFullfil = function(val){
const res = success(val)
nextSuccess(res)
}
const onReject = function(val){
const res = fail(val)
nextSuccess(res) ;
}
switch (this._status){
case "PENDING":
this._onSuccess.push(onFullfil)
this._onFail.push(onReject)
break;
case "RESOLVED":
onFullfil(this._value)
break;
case "REJECTED":
onReject(this._value)
break;
}
})
}
}
測試代碼:
const sp = new SimplePromise(function (res, rej){
setTimeout(function(){
let random = Math.random() * 10
random > 5 ? res(random) : rej(random)
}, 1000)
})
sp.then(data => {
console.log("more than 5 " + data)
return data
}, err =>{
console.log("less than 5 " + err)
return err
}).then((data) => {
console.log(data)
})
then的參數限制
完成了鏈式調用,then 方法還有許多其他限制:
下面思考 以下問題:代碼中四個使用 promise 的語句之間的不同點在哪兒?
假設 doSomething 也 doSomethingElse 都返回 Promise
doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);
doSomething().then(function () {
doSomethingElse();
}).then(finalHandler);;
doSomething().then(doSomethingElse()).then(finalHandler);;
doSomething().then(doSomethingElse).then(finalHandler);;
答案 一會兒再揭曉,我們先來梳理以下then 方法對傳入不同類型參數的處理機制:
直接上代碼:
class SimplePromise{
...
then(success, fail){
return new SimplePromise((nextSuccess, nextFail) => {
const onFullfil = function(val){
if(typeof success !== "function"){
nextSuccess(val)
}
else{
const res = success(val)//success 的返回值
if(res instanceof SimplePromise){//如果success 返回一個promise 對象
res.then(nextSuccess, nextFail)
}
else{
nextSuccess(res)
}
}
}
if(fail){
const onReject = function(val){
if(typeof fail !== "function"){
nextSuccess(val)
}
else{
const res = fail(val)
if(res instanceof SimplePromise){
res.then(nextSuccess, nextFail)
}
else{
nextSuccess(res)
}
}
}
}
else{
onReject = function(){}
}
switch (this._status){
case "PENDING":
this._onSuccess.push(onFullfil)
this._onFail.push(onReject)
break;
case "RESOLVED":
onFullfil(this._value)
break;
case "REJECTED":
onReject(this._value)
break;
}
})
}
}
對於傳入 then 方法的參數,首先判斷其是否爲 function,判斷爲否,直接執行 下一個 then 的 success 函數;判斷爲是,接着判斷函數的返回值 res 類型是否爲 Promise,如果爲否,直接執行下一個 then 的 success 函數,如果爲是,通過 then 調用接下來的函數。
所以,上面的問題就不難得到答案了。
<!-- 1 -->
doSomething().then(function () {
return doSomethingElse();//返回值爲Promise
}).then(finalHandler);
RETURN:
doSomething
--->doSomethingElse(undefined)
---> final(doSomethingElseResult)
<!-- 2 -->
doSomething().then(function () {
doSomethingElse();//返回值爲 undefined
}).then(finalHandler);
RETURN:
doSomething
--->doSomethingElse(undefined)
---> final(undefined)
<!-- 3 -->
doSomething().then(doSomethingElse())//參數 typeof != function
.then(finalHandler);
RETURN:
doSomething
doSomethingElse(undefined)
---> final(doSomethingResult)
<!-- 4 -->
doSomething().then(doSomethingElse)//與1的調用方式是不同的
.then(finalHandler);
RETURN:
doSomething
--->doSomethingElse(doSomethingResult)
---> final(doSomethingElseResult)
好,then 方法已經完善好了。
靜態函數
接下來是 Promise 的各種靜態函數
class SimplePromise(){
...
static all(){}
static race(){}
static resolve(){}
static reject(){}
}
all
static all(promiselist){
if(Array.isArray(promiselist)){
const len = promiselist.length;
const count = 0
const arr = []
return new SimplePromise((res, rej) => {
for(let i = 0; i<len; i++){
this.resolve(promiselist[i]).then(data => {
arr[i] = data
count ++
if(count === len){//每一個Promise都執行完畢後返回
res(arr)
}
}, err => {
rej(err)
})
}
})
}
}
race
static race(promiselist){
if(Array.isArray(promiselist)){
const len = promiselist.length
return new SimplePromise((res, rej) =>{
promiselist.forEach(item =>{
this.resolve(item).then(data => {
res(data)
}, err =>{
rej(err)
})
})
})
}
}
resolve
static resolve(obj){
if(obj instanceof SimplePromise){
return obj
}
else {
return new SimplePromise((res) =>{
res(obj)
})
}
}
reject
static reject(obj){
if(obj instanceof SimplePromise){
return obj
}
else {
return new SimplePromise((res, rej) =>{
rej(obj)
})
}
}
總結
現在,一個完整的 Promise 對象就完成了。現在來總結一下 callback 回調和 Promise 的異同吧。
其實,不管是 callback 還是 Promise,這二者都是將需要滯後
執行方法而提前聲明
的方式,只不過 callback 的處理方式比較粗獷,將 cb 函數放到異步執行的結尾;而 Promise 優於 cb 的是通過定義了不同的執行狀態,更加細緻的進行結果處理,提供了很好的 catch 機制,這是其一;其二,then 的鏈式調用解決了 cb 的回調地獄;但是 then 的鏈式調用也不是很好的解決方案,如果封裝不好,then裏面套用大量的代碼的話也會引起代碼的不美觀和閱讀上的困難,這一方面的終極解決方法還是 es7 的 async/await。
後記
這篇文章的代碼是幾個星期以前寫的,參考的是思否上的一篇關於promise的文章;總結的是我對promise的理解和思考,如果有不準確或錯誤的地方還希望各位不吝賜教!
參考文檔
談一談使用 Promise 的反模式<https://blog.csdn.net/kingppy...
寫這篇文章的時候,我是參考我兩週前的代碼寫的,當時的代碼思路來源於思否上的謀篇博客,等我找到會貼上來