Promise 是 ES6 新增的一個內置對象, 它是用來避免回調地獄的一種解決方案。git
從之前一直嵌套傳回調函數,到使用 Promise 來鏈式異步回調。Promise 到底是怎麼實現,從而達到回調函數「扁平化」?github
接下來就來一步步實現一個簡單的 Promise。開始發車了...數組
先來看一個使用 Promise 的簡單例子:promise
var p = new Promise(function a (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
})
p.then(function b (val) {
console.log(val);
});
複製代碼
代碼執行,它會先執行函數 a,打印出 1。定時器 1 秒後執行 resolve,緊接着執行了函數 b。異步
更詳細的步驟是這樣子的:函數
這裏的一個思路就是,在 then 函數執行時用一個屬性保存函數 b,而後在 resolve 執行時再將其執行。測試
這裏定義一個 MyPromise,它有 then 函數,還有一個 callback
屬性用來保存上面的「b 函數」。ui
function MyPromise (fn) {
var _this = this;
// 用來保存 then 傳入的回調函數
this.callback = undefined;
function resolve (val) {
_this.callback && _this.callback(val);
}
fn(resolve);
}
MyPromise.prototype.then = function (cb) {
this.callback = cb;
};
複製代碼
測試使用:this
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
});
p.then(function (val) {
console.log(val);
});
複製代碼
代碼執行時會立刻打印出 1,1秒後打印出 2。毛問題。spa
上面已經實現了一個簡單的 Promise,固然還有不少種狀況須要考慮。
好比會有這麼一種狀況,調用了多個 resolve 函數:
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
resolve(3);
resolve(4);
}, 1000);
});
複製代碼
原生的 Promise 在調用了第一個 resolve 以後,後面的 resolve 都無效化,即後面的 resolve 都是沒用的代碼。
這裏的處理方式是,給 MyPromise 再添加一個屬性 isResolved
,用來記錄是否調用過 resolve 函數。若是調用過,用它標識一下。再有 resolve 的調用,則用它判斷返回。
function MyPromise (fn) {
var _this = this;
this.callback = undefined;
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
_this.callback && _this.callback(val);
}
fn(resolve);
}
複製代碼
繼續走,除了能夠調用多個 resolve 函數,一樣咱們能夠調用多個 then 函數。
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
});
p.then(function (val) {
console.log(val);
});
p.then(function (val) {
console.log(val);
});
複製代碼
與 resolve 不一樣,這裏的每個傳給 then 的回調函數都會在 1 秒後執行,即 then 函數都有效。代碼執行,先打印出 1。1 秒後,打印兩個 2。
因此 MyPromise 的 callback 屬性需改爲數組
的格式,保存着每個 then 的回調函數。
function MyPromise (fn) {
var _this = this;
this.callback = [];
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
if (_this.callback.length > 0) {
_this.callback.forEach(function (func) {
func && func(val);
});
}
}
fn(resolve);
}
MyPromise.prototype.then = function (cb) {
this.callback.push(cb);
};
複製代碼
在屢次調用 then 時,MyPromise 經過屬性 callback 來保存多個回調函數。在 resolve 執行後,再去遍歷 callback,將它保存的回調函數逐個執行。
Promise 相對於回調地獄而言,它的優點在於能夠進行 then 的鏈式調用,從而將回調函數「扁平化」。
好比我有一個異步操做須要在另外一個異步操做後才能執行,只須要繼續調用 then 就可以下一步回調。
var p = new MyPromise(function (resolve) {
console.log(1);
setTimeout(function () {
resolve(2);
}, 1000);
});
p.then(function (val) {
console.log(val);
}).then(function (val) {
console.log(val);
});
複製代碼
既然要可以 then 鏈式調用,那我在執行完 then 函數後返回 this 不就能夠啦。但這不就跟剛剛的代碼同樣了嗎?
p.then(function (val) {
console.log(val);
});
p.then(function (val) {
console.log(val);
});
複製代碼
這樣就會在調用 resolve 函數以後同時執行,而不是執行完第一個 then 後,再執行第二個。因此直接返回 this 的方案是不行的。
咱們再想,還有什麼能夠返回的,而且帶有 then 函數的。答案就是 new 一個新的 MyPromise 並返回。咱們需重寫一個 then 函數的實現。
MyPromise.prototype.then = function (cb) {
var _this = this;
return new MyPromise(function (resolve) {
_this.callback.push({
cb: cb,
resolve: resolve
});
});
};
複製代碼
這裏 callback 從新改寫了一下,保存的是 then 的回調函數,和新 new 的 MyPromise 的 resolve 函數。保存的 resolve 函數先不執行,由於咱們知道,它一旦執行了,就會觸發傳入 then 的回調函數的執行。
同時,MyPromise 構造函數裏的 resolve 也須要調整一下:
function MyPromise (fn) {
var _this = this;
this.callback = [];
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
if (_this.callback.length > 0) {
_this.callback.forEach(function (item) {
var res;
var cb = item.cb;
var resolve = item.resolve;
cb && (res = cb(val));
resolve && resolve(res);
});
}
}
fn(resolve);
}
複製代碼
在執行第一個 then 的回調函數時,將其執行完返回的值,做爲保存的 resolve 的參數傳入。
p.then(function (val) {
console.log(val);
return val + 1;
}).then(function(val) {
console.log(val);
});
複製代碼
這樣子,就可以鏈式的 then 調用。先別急,咱們實現的只是 then 的同步
鏈式調用,而咱們最終要的是異步
的鏈式調用。
咱們須要這樣子的:
p.then(function (val) {
console.log(val);
return new MyPromise(function (resolve) {
setTimeout(function () {
resolve(val + 1);
}, 1000);
});
}).then(function(val) {
console.log(val);
});
複製代碼
先打印出 1,1秒後打印出第一個 then 裏的 2,再過多一秒,打印出第二個 then 的 3。
因此,咱們須要在取出原來保存的 cb 返回的值進行判斷。若是該值是一個 MyPromise 對象,則調用它的 then,不然跟原來同樣調用。
function MyPromise (fn) {
var _this = this;
this.callback = [];
this.isResolved = false;
function resolve (val) {
if (_this.isResolved) return;
_this.isResolved = true;
if (_this.callback.length > 0) {
_this.callback.forEach(function (item) {
var res;
var cb = item.cb;
var resolve = item.resolve;
cb && (res = cb(val));
if (typeof res === 'object' && res.then) {
res.then(resolve);
} else {
resolve && resolve(res);
}
});
}
}
fn(resolve);
}
複製代碼
在咱們實現的 MyPromise 裏,有兩個屬性,分別是 isResolved
和 callback
。isResolved 是一個標識,用來防止屢次調用 resolve。callback 是一個數組,用來保存回調函數。
MyPromise 還有一個 then 函數,用來處理異步後的回調,可以鏈式異步調用。
這樣子就實現了一個簡單的 Promise,完整的代碼戳 這裏。