動手實現一個簡單的promise

sxq111222發表於2018-05-24

昨天看了美團技術部落格的一篇講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

也就是說我們需要一個陣列,來存放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

可以看到,程式按照我們預想的方式執行了。

大家使用promise時可能會在xxx.then()註冊的回撥中返回一個promise物件,他的執行效果是這樣的(注意輸出的時間):

動手實現一個簡單的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)
});
複製程式碼

執行結果:

動手實現一個簡單的promise

可以看到程式執行正確,間隔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

我的 部落格

相關文章