為什么要自己造 Promise
- Promise已經(jīng)是Ecmascript 6標(biāo)準(zhǔn)中的內(nèi)容
- Promise是async & await的基礎(chǔ)
- 不造一下亭病,怎么才能把Promise/A+的標(biāo)準(zhǔn)了解的更透徹?
本文結(jié)構(gòu)
- Promise認(rèn)知 - 40%
- Promise輪子 - 60%
對(duì)Promise有足夠了解的讀者雇卷,可酌情閱讀跳過(guò)認(rèn)知部分。
Promise的認(rèn)知階梯
最早開(kāi)始使用Promise是為了不寫(xiě)回調(diào)形式的函數(shù)崎淳;后來(lái)發(fā)現(xiàn)Promise可以讓nodejs里function (err, data)
的寫(xiě)法變得更友好柜与;后來(lái)開(kāi)始用 q;再后來(lái)發(fā)現(xiàn) bluebird 這樣順應(yīng) Promise/A+ 的API用起來(lái)更順悄蕾。然后發(fā)現(xiàn)bluebird這個(gè)詞原本就是一個(gè)combinator票顾,compose其實(shí)是它的本意;然后發(fā)現(xiàn)Promise也是一種Monad帆调。
以上是筆者自己對(duì)Promise的認(rèn)知之路奠骄,理解上不斷產(chǎn)生新的變化,其中真正可以提煉出來(lái)的步驟如下:
Phase 1 - Callback Hell 救星
首先番刊,callback是異步操作的產(chǎn)物含鳞,同步操作不需要回調(diào)函數(shù)。早先在純?yōu)g覽器環(huán)境中芹务,會(huì)用到callback的有:
- ajax請(qǐng)求
- DOM事件綁定
- setTimeout
從nodejs開(kāi)始蝉绷,異步IO是根本特性鸭廷,因而callback變得無(wú)處不在,然后出現(xiàn)了nodejs的callback參數(shù)標(biāo)準(zhǔn):function (err, data)
/*
* callback version
*/
var sillyCopy = function (src, target, callback) {
fs.readFile(src, function (err, data) {
if (err) return callback(err);
fs.write(target, data, function (err) {
if (err) return callback(err);
fs.write(src, 'this is silly', function (err) {
callback(err);
});
});
});
sillyCopy('my/dir/src.txt', 'my/dir/target.txt', function (err) {
if (err) console.log(err);
else console.log('done');
});
/*
* promise version
*/
var sillyCopy = function (src, target) {
return promiseFS.readFile(src)
.then(function (data) {
return promiseFS.writeFile(target));
})
.then(function() {
return promiseFS.writeFile(src, 'this is silly');
});
};
sillyCopy('my/dir/src.txt', 'my/dir/target.txt')
.then(function (data) {
console.log(data);
}, function (err) {
console.log(err);
});
注: 以上 promise 版本的 sillyCopy 中用到了promise化的 fs熔吗。
可以看到辆床,使用了promise之后的異步操作定義,變得更加更加清晰
Phase 2 - 串聯(lián)異步操作
var flow = function (list) {
return function (arg) {
return list.reduce(function (p, fn) {
return p.then(fn);
}, Promise.resolve(arg));
};
var sillyProcess = flow([
pseudo_ReadFile,
pseudo_AjaxPostSearchFileText,
pseudo_ReadDB,
pseudo_WriteToLocalFile
])
這里的flow就是同步模式下用到的 compose桅狠,用于將各種異步操作進(jìn)行串聯(lián)讼载,在充滿異步操作充滿Promise的環(huán)境中,一個(gè)簡(jiǎn)單的flow實(shí)現(xiàn)可以讓代碼變得清晰易懂
Phase 3 - 異常捕獲
回看之前的樣例代碼垂攘,callback形式會(huì)讓對(duì)異常的捕獲散布在各層級(jí)的callback里维雇,看著鬧心有沒(méi)有?
var sillyCopy = function (src, target, callback) {
fs.readFile(src, function (err, data) {
if (err) return callback(err);
fs.write(target, data, function (err) {
if (err) return callback(err);
fs.write(src, 'this is silly', function (err) {
callback(err);
});
});
});
// This is what we want
sillyCopy(src, target)
.catch(function (err) {
console.log(err);
});
在實(shí)際Coding過(guò)程中晒他,異常處理是非常重要的步驟吱型,除了上面看到的串行的異步代碼,還有并行的異步操作陨仅,各種復(fù)雜情況導(dǎo)致對(duì)異常的統(tǒng)一處理變得十分關(guān)鍵津滞,僅異常處理的代碼會(huì)變得更容易維護(hù)。
Phase 4 - Thenable 接口
Thenable在一般場(chǎng)景中我們碰到的不算特別多灼伤,但它的價(jià)值卻不能被低估触徐。Thenable的價(jià)值在于,它是一個(gè)統(tǒng)一的接口定義狐赡,它讓我們可以把不同Library里的撞鹉、使用不同Promise實(shí)現(xiàn)的異步操作,放在一起使用颖侄。
例如鸟雏,jQuery有它自己內(nèi)部對(duì)Promise的實(shí)現(xiàn) (早起使用 defer 創(chuàng)建 Promise 對(duì)象 ,后來(lái)慢慢統(tǒng)一到了 Promise/A+ 的標(biāo)準(zhǔn)模式)览祖。還有 npm 上成千上萬(wàn)的包孝鹊,都可能使用了不同的 Promise 實(shí)現(xiàn)。
開(kāi)始造 Promise
既然是造輪子展蒂,先看下按照什么標(biāo)準(zhǔn)來(lái)造又活,然后確定一下造的步驟,剩下的就是開(kāi)始coding了锰悼。
標(biāo)準(zhǔn)
步驟
我們把實(shí)現(xiàn)過(guò)程分成3部分柳骄,類(lèi)似于對(duì)Promise的認(rèn)知過(guò)程
- 串聯(lián)異步操作,忽略異常處理
- 構(gòu)造函數(shù)
- 保留 resolve 結(jié)果
- then箕般,要滿足 resolve 之后的 then 也能得到執(zhí)行
- 異常處理
- then增加reject參數(shù)
- 捕獲 resolve 和 reject 過(guò)程中的異常
- Thenable
- 允許返回一個(gè) Thenable 的對(duì)象
- 對(duì) Thenable.then(resolve, reject) 的整體過(guò)程進(jìn)行異常捕獲
串聯(lián)異步操作
- 一個(gè)Promise對(duì)象可以多次調(diào)用 then
- 保存 resolve 結(jié)果
var MyPromise = function (executor) {
var self = this;
this.next = [];
this._value = null;
this._state = 0;
var resolve = function (value) {
self._state = 1;
self._value = value;
self.next.forEach(function (fn) {
fn(value);
});
};
executor(resolve);
};
MyPromise.prototype.then = function (onResolve) {
var self = this;
if (!self._state) {
return MyPromise.resolve(onResolve(self._value));
}
return new Promise(function (resolve) {
self.next.push(function (value) {
resolve(onResolve(value));
});
});
};
MyPromise.resolve = function (value) {
return new Promise(function (resolve) {
resolve(value);
});
};
/*
* Demo
*/
var p = Promise.resolve(2);
p
.then(function (a) { return a * 2 })
.then(function (a) { console.log(a) });
// output: 4
setTimeout(function () {
p
.then(function (a) { return a * 3 })
.then(function (a) { console.log(a) });
// output: 6
}, 100);
至此夹界,串聯(lián)異步操作部分就算完成了,一個(gè)不帶 reject 和 異常捕獲的 Thenable 實(shí)例隘世。從上面的 Demo 可以看出可柿,我們的 Promise API 已初具雛形。但不要得意的太早丙者,Promise 的 API 本身并不復(fù)雜:
- Promise API
- Promise Constructor
- Promise.prototype.then
- Promise.prototype.catch
- Promise.all
- Promise.resolve
- Promise.reject
不過(guò)同等功能下复斥,越是簡(jiǎn)單的 API 設(shè)計(jì),其內(nèi)部邏輯就會(huì)越復(fù)雜械媒。jQuery.$ 就是一個(gè)很好的例子目锭。
異常捕獲
- then 接受兩個(gè)參數(shù), onResolve & onReject
- 對(duì) onResolve 和 onReject 執(zhí)行過(guò)程中產(chǎn)生的異常進(jìn)行捕獲
var PENDING = 0;
var RESOLVED = 1;
var REJECTED = 2;
// Note: onResolve 和 onReject 都會(huì)在這里包一層
// 為了讓兩者的返回值都能繼續(xù)向下傳遞到下一個(gè) onResolve
var wrapHandler = function (state, handler, p2) {
return function (val) {
var next = state == RESOLVED ? p2._resolve : p2._reject;
var ret, then, type, p3;
if (!handler || typeof handler !== 'function') {
return next(val);
}
try {
ret = handler(val);
} catch (e) {
return p2._reject(e);
}
p2._resolve(ret);
};
};
var genHandler = function (state, p) {
return function (value) {
if (p._state !== PENDING) return;
p._state = state;
p._value = value;
p.next.forEach(function (obj) {
setTimeout(function () {
obj[state === RESOLVED ? 'onResolve' : 'onReject'](value);
}, 0);
});
};
};
var MyPromise = function (executor) {
var self = this;
this.next = [];
this._value = null;
this._state = PENDING;
this._resolve = genHandler(RESOLVED, self);
this._reject = genHandler(REJECTED, self);
executor(this._resolve, this._reject);
};
MyPromise.prototype.then = function (onResolve, onReject) {
var self = this;
var p2 = new MyPromise(function () {});
var ret;
if (self._state === PENDING) {
self.next.push({
onResolve: wrapHandler(RESOLVED, onResolve, p2),
onReject: wrapHandler(REJECTED, onReject, p2)
});
} else {
setTimeout(function () {
wrapHandler(self._state, self._state === RESOLVED ? onResolve : onReject, p2)(self._value);
}, 0);
}
return p2;
};
MyPromise.resolve = function (value) {
return new Promise(function (resolve, reject) {
resolve(value);
});
};
MyPromise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
/*
* Demo
*/
MyPromise.reject(1)
.then(null, function (e) {
console.log(e);
return e + 2;
})
.then(function (n) {
console.log(n);
throw n * 2;
})
.then(null, function (e) {
console.log(e);
});
// output: 1
// output: 3
// output: 6
這里有幾個(gè)非常重要的 Promise 使用方法:
- 沒(méi)有設(shè)置 onResolve 或 onReject纷捞,對(duì)應(yīng)的返回值或異常將原封不動(dòng)向下傳遞
Promise.resolve(1) .then() .then(function (n) { console.log(n); }); // output: 1 Promise.reject(2) .then() .then(null, function (e) { console.log(e); }); // output: 2
- onReject 的返回值會(huì)繼續(xù)向下傳遞給下一個(gè) onResolve
Promise.reject(1) .then(null, function (e) { return e + 2; }) .then(function (n) { console.log(n); }; // output: 3
Thenable 接口
- Thenable 類(lèi)型的處理
- 出現(xiàn)返回Promise實(shí)例自身的情況痢虹,要拋出 TypeError
var PENDING = 0;
var RESOLVED = 1;
var REJECTED = 2;
var wrapHandler = function (state, handler, p2) {
return function (val) {
var next = state == RESOLVED ? p2._resolve : p2._reject;
var ret, then, type, p3;
if (!handler || typeof handler !== 'function') {
return next(val);
}
try {
ret = handler(val);
} catch (e) {
return p2._reject(e);
}
solve(ret, p2);
};
};
// 處理所有可能情況的 val,包括 Thenable
var solve = function (val, p2) {
if (val === p2) {
return p2._reject(new TypeError('no promise circle allowed'));
}
type = typeof val;
if (type === 'function' || type === 'object' && val !== null) {
try {
then = val && val.then;
} catch (e) {
return p2._reject(e);
}
if (typeof then === 'function') {
try {
return then.call(val, function (val2) {
solve(val2, p2);
}, p2._reject);
} catch (e) {
return p2._reject(e);
}
}
}
return p2._resolve(val);
}
var genHandler = function (state, p) {
return function (value) {
if (p._state !== PENDING) return;
p._state = state;
p._value = value;
p.next.forEach(function (obj) {
setTimeout(function () {
obj[state === RESOLVED ? 'onResolve' : 'onReject'](value);
}, 0);
});
};
};
var MyPromise = function (executor) {
var self = this;
this.next = [];
this._value = null;
this._state = PENDING;
this._resolve = genHandler(RESOLVED, self);
this._reject = genHandler(REJECTED, self);
executor(this._resolve, this._reject);
};
MyPromise.prototype.then = function (onResolve, onReject) {
var self = this;
var p2 = new MyPromise(function () {});
var ret;
if (self._state === PENDING) {
self.next.push({
onResolve: wrapHandler(RESOLVED, onResolve, p2),
onReject: wrapHandler(REJECTED, onReject, p2)
});
} else {
setTimeout(function () {
wrapHandler(self._state, self._state === RESOLVED ? onResolve : onReject, p2)(self._value);
}, 0);
}
return p2;
};
MyPromise.resolve = function (value) {
return new Promise(function (resolve, reject) {
resolve(value);
});
};
MyPromise.reject = function (value) {
return new Promise(function (resolve, reject) {
reject(value);
});
};
到此為止主儡,一個(gè)滿足 Promise/A+ 規(guī)范的 MyPromise 就算造完了奖唯。
完整代碼在我的 github 上可以找到