簡體   English   中英

在JavaScript中實現Promise類

[英]Implementing promise class in javascript

我正在嘗試使用可鏈接的.then()功能在JavaScript中實現一個簡單的promise類。 這是我到目前為止所做的-

class APromise {
    constructor(Fn) {
        this.value = null;
        Fn(resolved => { this.value = resolved; });
    }
    then(fn) {
        fn(this.value);
        return this;
    }
}

function myFun() {
    return new APromise(resolve => {
        // just resolve('Hello'); works but this doesn't
        setTimeout(() => { resolve('Hello'); }, 2000);
    });
}

const log = v => { console.log(v); };

myFun().then(log).then(log);

輸出-

null
null

而不是'Hello' 2次。 我認為它目前正在忽略setTimeout()調用,我應該如何使其工作?

問題

您的代碼無法按照您想要的方式工作,因為您將異步流與同步流混合在一起。

當您調用.then() ,它將同步返回this 由於setTimeout()是一個異步函數,會在一段時間(2秒)后調用,因此this.value仍然為null

如果您想進一步了解JS的異步流程,建議您觀看此視頻 它有點長,但超級有幫助。


使您的代碼正常工作

由於我們無法確定setTimeout()何時調用傳遞給它的函數,因此我們無法調用和回調依賴於其操作的函數。 我們將這些回調存儲在數組中,以備后用。

當調用setTimeout()函數(承諾解決)時,我們得到了承諾解決的結果。 因此,我們現在調用所有綁定的回調。

class APromise {
    constructor(Fn) {
        this.value = null;
-       Fn(resolved => { this.value = resolved; });
+       this.callbacks = [];
+       Fn(resolved => {
+           this.value = resolved;
+
+           this.callbacks.forEach(cb => {
+               cb(this.value);
+           });
+       });
    }
    then(fn) {
-       fn(this.value);
+       this.callbacks.push(fn);
        return this;
    }
}

function myFun() {
    return new APromise(resolve => {
        setTimeout(() => { resolve('Hello'); }, 2000);
    });
}

const log = v => { console.log(v); };

myFun().then(log).then(log);

連鎖問題

上面的代碼部分解決了該問題。

當一個回調的結果傳遞到下一個回調時,可以實現真正的鏈接。 在當前代碼中情況並非如此。 為了實現這一點,每個.then(cb)必須返回一個新的APromise ,它可以在調用cb函數時解析。

完整且符合Promises / A +的實現方式超出了單個SO答案的范圍,但這不應給人以為它不可行的印象。 這是精選的定制命令列表


更全面的實施

讓我們從一個干凈的開始。 我們需要一個Promise類,該類實現一個方法, then該方法還返回一個允許鏈接的承諾。

class Promise {
    constructor(main) {
        // ...
    }

    then(cb) {
        // ...
    }
}

在這里, main是一個函數, 它將一個函數作為參數,並在promise被解析/實現時調用它-我們稱此方法resolve() 所述函數resolve()由我們的Promise類實現並提供。

function main(resolve) {
    // ...
    resolve(/* resolve value */);
}

then()方法的基本功能是,一旦諾言實現,就用諾言值觸發/激活提供的回調函數cb()

考慮到這兩點,我們可以重新連接Promise類。

class Promise {
    constructor(main) {
        this.value = undefined;
        this.callbacks = [];

        const resolve = resolveValue => {
            this.value = resolveValue;

            this.triggerCallbacks();
        };

        main(resolve);
    }

    then(cb) {
        this.callbacks.push(cb);
    }

    triggerCallbacks() {
        this.callbacks.forEach(cb => {
            cb(this.value);
        });
    }
}

我們可以使用tester()函數測試當前代碼。

(function tester() {
    const p = new Promise(resolve => {
        setTimeout(() => resolve(123), 1000);
    });

    const p1 = p.then(x => console.log(x));
    const p2 = p.then(x => setTimeout(() => console.log(x), 1000));
})();

// 123 <delayed by 1 second>
// 123 <delayed by 1 more second>

到此為止我們的基礎。 現在,我們可以實現鏈接。 我們面臨的最大問題是, then()方法必須同步返回promise,該promise將異步解決。

我們需要等待父承諾解決,然后才能解決下一個承諾 這意味着,除了向父諾言中添加cb() ,我們還必須添加下一個諾言resolve()方法,該方法使用cb()返回值作為resolveValue

then(cb) {
-   this.callbacks.push(cb);
+   const next = new Promise(resolve => {
+       this.callbacks.push(x => resolve(cb(x)));
+   });
+
+   return next;
}

如果這最后一點使您感到困惑,那么這里有一些提示:

  • Promise構造函數接受main()函數作為參數
  • main()將函數resolve()作為參數
    • resolve()Promise構造函數提供
  • resolve()任何類型的參數用作resolveValue

演示版

class Promise {
    constructor(main) {
        this.value = undefined;
        this.callbacks = [];

        const resolve = resolveValue => {
            this.value = resolveValue;

            this.triggerCallbacks();
        };

        main(resolve);
    }

    then(cb) {
        const next = new Promise(resolve => {
            this.callbacks.push(x => resolve(cb(x)));
        });

        return next;
    }

    triggerCallbacks() {
        this.callbacks.forEach(cb => {
            cb(this.value);
        });
    }
}

(function tester() {
    const p = new Promise(resolve => {
        setTimeout(() => resolve(123), 1000);
    });

    const p1 = p.then(x => console.log(x));
    const p2 = p.then(x => setTimeout(() => console.log(x), 1000));
    const p3 = p2.then(x => setTimeout(() => console.log(x), 100));
    const p4 = p.then((x) => new Promise(resolve => {
        setTimeout(() => resolve(x), 1000);
    }))

    /*
        p: resolve after (1s) with resolveValue = 123
        p1: resolve after (0s) after p resolved with resolveValue = undefined
        p2: resolve after (0s) after p resolved with resolveValue = timeoutID
        p3: resolve after (0s) after p2 resolved with resolveValue = timeoutID
        p4: resolve after (1s) after p resolved with resolveValue = Promise instance
    */
})();

// 123  <delayed by 1s>
// 2    <delayed by 1.1s>
// 123  <delayed by 2s>

為舊的僵屍瀏覽器(ES3或更高版本)實施

我在客戶端js中使用Promise ,但在舊的瀏覽器中卻發現該類不存在。 因此,我為他們實施了具有resolvereject方法的resolve

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function"); 
    }
}
var Promise = function () {
    function Promise(main) {
        var _this = this;

        _classCallCheck(this, Promise);

        this.value = undefined;
        this.callbacks = [];

        var resolve = function resolve(resolveValue) {
            _this.value = resolveValue;

            _this.triggerCallbacks();
        };
        var reject = function reject(rejectValue) {
            _this.value = rejectValue;

            _this.triggerCallbacks();
        };
        main(resolve, reject);
    }

    Promise.prototype.then = function then(cb) {
        var _this2 = this;

        var next = new Promise(function (resolve) {
            _this2.callbacks.push(function (x) {
                return resolve(cb(x));
            });
        });

        return next;
    };

    Promise.prototype.catch = function catch_(cb) {
        var _this2 = this;

        var next = new Promise(function (reject) {
            _this2.callbacks.push(function (x) {
                return reject(cb(x));
            });
        });

        return next;
    };

    Promise.prototype.triggerCallbacks = function triggerCallbacks() {
        var _this3 = this;

        this.callbacks.forEach(function (cb) {
            cb(_this3.value);
        });
    };

    return Promise;
}();

解決該問題,在Promise中進行通話同步時:

class MyPromise{
    constructor(fn){
        this.callback= null;
        this.data= null;
        this.calledInNext= false;
        fn((data, state)=>{ // unsafe when call resolve({}, 'string')
            this.calledInNext= (state === 'CALLED_IN_NEXT') ? true : false;
            this.data= data;
            if(this.callback) this.callback(this.data);
        }, function(_err){
            console.log('resolve({error}) to without catch')
        })
    }
    then(cb){ // next
        if(this.data || this.calledInNext){
            return new MyPromise(r => {
                r(cb(this.data), 'CALLED_IN_NEXT');
            });
        } else {
            return new MyPromise(r => {
                this.callback = x=> r(cb(x))
            })       
        }
    }
}

或連鎖:

class MyPromise{
    constructor(fn){
        this.callbacks= [];
        this.data= null;
        fn((data)=>{
            this.data= data;
            var gg= this.data;
            this.callbacks.forEach(el=>{
                gg= el(gg);
            })
        })
    }
    then(cb){
        if(this.data || this._calledInNext){
            this._calledInNext= true; this.data= cb(this.data); return this;
        } else {
            this.callbacks.push(cb); return this;
        }
    }
}

測試:

(new MyPromise(function(resolve, reject){
    // setTimeout(resolve, 1000, {done: 1})
    resolve({done: 1})
})).then(data=>{
    console.log(data);      // {done: 1}
    return data;
}).then(data=>{
    console.log(data);      // {done: 1}
    return {};
}).then(data=>{
    console.log(data);      // {}
}).then(data=>{
    console.log(data);      // undefine
}).then(data=>{
    console.log(data);      // undefine
}).then(data=>{
    console.log(data);      // undefine
})

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM