昨天看了美團技術部落格的一篇講promise的文章,裡面實現了一個簡單的promise,在這裡鞏固並修改。
promise的使用是這樣的:
let promise_1 = new myPromise((resolve, reject) => {
setTimeout(() => { resolve(100); }, 1000);
});
promise_1.then(rst => {
console.log( rst);
});
複製程式碼
promise大家平時經常用到,下面我們嘗試實現一個簡單的promise。
為了方便,假設promise_1是一個promise例項。
建構函式:
function myPromise(fn) {
let status = 'pending';//promise物件的狀態
let val;//promise物件的值
}
複製程式碼
首先,promise物件在例項化時要求必須傳入一個函式。
function required(name){
throw new Error('param '+name+' is required')
}
function myPromise(fn = required('fn')) {
let status = 'pending';//promise物件的狀態
let val;//promise物件的值
fn();
}
複製程式碼
required函式的作用是在使用者沒傳入引數時會報錯。
然後,promise物件在例項化時會同步的執行傳入的函式(fn)。
promise的回掉函式要通過promise_1.then()來註冊,並且promise_1.then()返回的也應該是一個promise,這樣才能實現鏈式呼叫。
修改一下程式碼:
function myPromise(fn = required('fn')) {
let status = 'pending';//promise物件的狀態
let val;//promise物件的值
this.then = (onResolve,onReject)=>{
return new myPromise((resolve, reject)=>{
//我是promise_return
});
}
fn();
}
複製程式碼
現在我們的promise_1有了then,then()也返回一個promise(我們叫他promise_return)。但是這兩個promise並沒有什麼直接關係。
我們現在嘗試實現這樣的效果:
也就是說我們需要一個陣列,來存放promise例項的多個回撥。
function myPromise(fn = required('fn')) {
let next_promiseInfos = [];//當前promise對像的回撥們
let status = 'pending';//promise物件的狀態
let val;//promise物件的值
this.then = (onResolve,onReject)=>{
return new myPromise((resolve, reject)=>{
//我是promise_return
});
}
fn();
}
複製程式碼
現在我們想想如何將當前promise和他的多個回撥相關聯。
首先,如果當前promise_1物件狀態為pending,此時通過promise_1.then(onResolve)給它新增一個回撥,這個onResolve不會立刻執行,而是等待promise_1物件呼叫resolve並且自身狀態變為resolved後,才會執行。我們實現一個handle方法,它會將promise物件的每個回撥的相關資訊存在陣列中。
function myPromise(fn = required('fn')) {
let next_promiseInfos = [];//當前promise對像的回撥們
let status = 'pending';//promise物件的狀態
let val;//promise物件的值
this.then = (onResolve,onReject)=>{
return new myPromise((resolve, reject)=>{
//我是promise_return
handle({
resolve, reject, onResolve, onReject
});
});
}
function handle(promise_info) {
if (status === 'pending') {
next_promiseInfos.push(promise_info);
return;
}
}
fn();
}
複製程式碼
我們可以看到,將onResolve和resolve存入promise_1的next_promiseInfos陣列中(關於reject的先不討論)。
現在我們的promise_1有了各個回撥的相關資訊(onResolve和每個promise_return的resolve)。我們想一想在promise_1呼叫resolve時應該發生什麼。
promise_1呼叫resolve時,promise_1的值(程式碼中的 val)會變成傳入resolve函式的值,peomise_1的狀態變為resolved,最後,執行promise_1的next_promiseInfos陣列中每個元素的onResolve回撥,並且為了滿足鏈式呼叫,需要將回撥的執行結果傳給下一個promise
我們實現resolve函式:
function resolve(value) {
val = value;
status = 'resolved';
doCallbacks();
}
function doCallbacks() {
setTimeout(() => {
next_promiseInfos.forEach(promise_info => {
handle(promise_info);
});
}, 0);
}
複製程式碼
對handle函式稍作修改:
function handle(promise_info) {
if (status === 'pending') {
next_promiseInfos.push(promise_info);
return;
}
rst = promise_info.onResolve(val);//執行回撥並返回結果
promise_info.resolve(rst);//將返回的結果傳入下一個promise
}
複製程式碼
大家對最後一行程式碼可能不明白,他的作用就是將回撥執行的結果傳給promise_1.then()生成的新promise,這樣就可以實現鏈式呼叫。
我們先測試一下現在的程式碼:
function required(name) {
throw new Error('param ' + name + ' is required')
}
function myPromise(fn = required('fn')) {
let next_promiseInfos = [];//當前promise對像的回撥們
let status = 'pending';//promise物件的狀態
let val;//promise物件的值
this.then = (onResolve, onReject) => {
return new myPromise((resolve, reject) => {
//我是promise_return
handle({
resolve, reject, onResolve, onReject
});
});
}
function resolve(value) {
val = value;
status = 'resolved';
doCallbacks();
}
function doCallbacks() {
setTimeout(() => {
next_promiseInfos.forEach(promise_info => {
handle(promise_info);
});
}, 0);
}
function handle(promise_info) {
if (status === 'pending') {
next_promiseInfos.push(promise_info);
return;
}
rst = promise_info.onResolve(val);//執行回撥並返回結果
promise_info.resolve(rst);//將返回的結果傳入下一個promise
}
fn(resolve);
}
複製程式碼
測試程式碼:
console.log('程式執行')
let promise_1 = new myPromise((resolve, reject) => {
setTimeout(() => { resolve(100); }, 1000);
});
promise_1.then(rst => {
console.log(1, rst);
});
promise_1.then(rst => {
console.log(2, rst);
});
promise_1.then(rst => {
console.log(3, rst);
});
promise_1.then(rst => {
console.log(4, rst);
return rst +11;
}).then(rst=>{
console.log('鏈式呼叫',rst)
return rst+2;
}).then(rst=>{
console.log('鏈式呼叫2',rst)
});
複製程式碼
執行結果:
可以看到,程式按照我們預想的方式執行了。
大家使用promise時可能會在xxx.then()註冊的回撥中返回一個promise物件,他的執行效果是這樣的(注意輸出的時間):
在程式執行1秒後,輸出1 100,然後該回撥返回一個promise,這個promise兩秒後resolve(100+2),最後一個回撥的實參就是100+2。
我們如何實現這個回撥中返回promise的效果呢?
目前,我們實現的promise在resolve後,直接就將狀態改為resolve,並且執行後面的回撥。這種方式肯定是不行的了。我們在resolve時應該判斷傳入引數的型別,如果是promise型別,那麼我們就等待這個promise執行完成之後再執行resolve。這樣才能滿足上面說的效果。 我們修改一下resolve的程式碼:
function resolve(value) {
//判斷一下value的型別
if (typeof value === 'object' && value.then && typeof value.then === 'function') {
//如果看起來像一個promise,我們就當他是promise
value.then(resolve);
return;
}
val = value;
status = 'resolved';
doCallbacks();
}
複製程式碼
如果傳入promise_1的resolve函式的value也是一個promise(暫時叫他value_promise),那麼我們給他註冊一個回撥。並且,回撥函式就是resolve。
如果value_promise執行完成,他的回撥函式會被執行,並將value_promise的值傳給回撥函式(promise_1的resolve)。也就是說,在value_promise呼叫他自己的resolve(xxxValue)後,會呼叫promise_1的resolve(xxxValue)。
這樣,promise_1就得到了value_promise的值。
現在的程式碼:
function required(name) {
throw new Error('param ' + name + ' is required')
}
function myPromise(fn = required('fn')) {
let next_promiseInfos = [];//當前promise對像的回撥們
let status = 'pending';//promise物件的狀態
let val;//promise物件的值
this.then = (onResolve, onReject) => {
return new myPromise((resolve, reject) => {
//我是promise_return
handle({
resolve, reject, onResolve, onReject
});
});
}
function resolve(value) {
//判斷一下value的型別
if (typeof value === 'object' && value.then && typeof value.then === 'function') {
//如果看起來像一個promise,我們就當他是promise
value.then(resolve);
return;
}
val = value;
status = 'resolved';
doCallbacks();
}
function doCallbacks() {
setTimeout(() => {
next_promiseInfos.forEach(promise_info => {
handle(promise_info);
});
}, 0);
}
function handle(promise_info) {
if (status === 'pending') {
next_promiseInfos.push(promise_info);
return;
}
rst = promise_info.onResolve(val);//執行回撥並返回結果
promise_info.resolve(rst);//將返回的結果傳入下一個promise
}
fn(resolve);
}
複製程式碼
測試程式碼:
console.log('程式執行')
let promise_1 = new myPromise((resolve, reject) => {
setTimeout(() => { resolve(100); }, 1000);
});
promise_1.then(rst => {
console.log('aaa', rst);
return new myPromise((resolve)=>{
setTimeout(()=>{
resolve(rst+1)
},1000)
})
}).then(rst => {
console.log('bbb', rst);
return new myPromise((resolve)=>{
setTimeout(()=>{
resolve(rst+1)
},1000)
})
}).then(rst => {
console.log('ccc', rst);
return new myPromise((resolve)=>{
setTimeout(()=>{
resolve(rst+1)
},1000)
})
}).then(rst=>{
console.log('finally',rst)
});
複製程式碼
執行結果:
可以看到程式執行正確,間隔1秒輸出結果。
目前,我們已經實現了可以resolve的promise。應該已經理解了實現一個promise的原理。
catch和reject的實現並不複雜,如果有需要會再寫一個文章介紹如何實現。
完整的程式碼:
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>promise</title>
</head>
<body>
<script>
function required(name){
throw new Error('param '+name+' is required')
}
function myPromise(fn = required('fn')) {
let next_promiseInfos = [];
let status = 'pending';
let val;
let catcher = null;
this.then = (onResolve, onReject) => {
return new myPromise((resolve, reject) => {
handle({
resolve, reject, onResolve, onReject
});
});
}
this.catch = (cb) => {
catcher = cb;
}
function resolve(value) {
if (typeof value === 'object' && value.then && typeof value.then === 'function') {
value.then(resolve,reject);
return;
}
val = value;
status = 'resolved';
doCallbacks();
}
function doCallbacks() {
setTimeout(() => {
next_promiseInfos.forEach(promise_info => {
handle(promise_info);
});
}, 0);
}
function reject(err) {
status = 'reject';
val = err;
if (catcher) {
catcher(err);
return;
}
if (!catcher && next_promiseInfos.length < 1) {
throw new Error('uncaught reject:' + err);
}
doCallbacks();
}
function handle(promise_info) {
if (status === 'pending') {
next_promiseInfos.push(promise_info);
return;
}
if (status === 'reject') {
let cb = promise_info.onReject ? promise_info.onReject : promise_info.reject;
cb(val);
return;
}
let rst;
try{
rst = promise_info.onResolve(val);
}catch(err){
promise_info.reject(err);
return;
}
promise_info.resolve(rst);
}
fn(resolve, reject);
}
console.log('程式執行')
let promise_1 = new myPromise((resolve, reject) => {
setTimeout(() => { resolve(100); }, 1000);
});
promise_1.then(rst => {
console.log(1,rst);
return new myPromise((resolve,reject)=>{
setTimeout(()=>{
resolve(rst +2);
},2000);
});
}).then(rst=>{
console.log('wait',rst);
throw new Error('出錯了');
}).then(rst=>{
console.log(rst);
}).catch(err=>{
console.log('catch 到錯誤了',err)
});
</script>
</body>
</html>
複製程式碼
參考:https://tech.meituan.com/promise-insight.html
我的 github
我的 部落格