简体   繁体   English

在JavaScript中实现Promise类

[英]Implementing promise class in javascript

I'm trying to implement a simple promise class with chainable .then() functionality in javascript. 我正在尝试使用可链接的.then()功能在JavaScript中实现一个简单的promise类。 Here's what I've done till now - 这是我到目前为止所做的-

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);

This outputs - 输出-

null
null

Instead of 'Hello' 2 times. 而不是'Hello' 2次。 I think it is currently ignoring setTimeout() call, how should I make this work? 我认为它目前正在忽略setTimeout()调用,我应该如何使其工作?

The problem 问题

Your code isn't working the way you want because you are mixing async flow with sync flow. 您的代码无法按照您想要的方式工作,因为您将异步流与同步流混合在一起。

When you call .then() , it will return this synchronously. 当您调用.then() ,它将同步返回this Since setTimeout() is an async function that is called after some time (2 secs), this.value is still null . 由于setTimeout()是一个异步函数,会在一段时间(2秒)后调用,因此this.value仍然为null

If you want to know more about the asynchronous flow of JS, I recommend watching this video . 如果您想进一步了解JS的异步流程,建议您观看此视频 It's a bit long but super helpful. 它有点长,但超级有帮助。


Making your code work 使您的代码正常工作

Since we can't tell when setTimeout() will call the function passed to it, we cannot call and callback functions that depend on its operations. 由于我们无法确定setTimeout()何时调用传递给它的函数,因此我们无法调用和回调依赖于其操作的函数。 We store these callbacks in an array for later consumption. 我们将这些回调存储在数组中,以备后用。

When the setTimeout() function is called (promise resolves), we have the result of promise resolution. 当调用setTimeout()函数(承诺解决)时,我们得到了承诺解决的结果。 And so, we now call all the bound callbacks. 因此,我们现在调用所有绑定的回调。

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);

The chaining problem 连锁问题

The code above solves the problem partially. 上面的代码部分解决了该问题。

True chaining is achieved when the result of one callback is passed on to the next. 当一个回调的结果传递到下一个回调时,可以实现真正的链接。 That's not the case in our current code. 在当前代码中情况并非如此。 TO make that happen, each .then(cb) must return a new APromise that resolves when the cb function is called. 为了实现这一点,每个.then(cb)必须返回一个新的APromise ,它可以在调用cb函数时解析。

A complete and Promises/A+ conformant implementation is way over the scope of a single SO answer, but that shouldn't give the impression that it isn't doable. 完整且符合Promises / A +的实现方式超出了单个SO答案的范围,但这不应给人以为它不可行的印象。 Here's a curated list of custom implentations . 这是精选的定制命令列表


Fuller implementation 更全面的实施

Let's start with a clean slate. 让我们从一个干净的开始。 We need a class Promise that implements a method then which also returns a promise to allow chaining. 我们需要一个Promise类,该类实现一个方法, then该方法还返回一个允许链接的承诺。

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

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

Here, main is a function that takes a function as an argument and calls it when the promise is resolved/fulfilled - we call this method resolve() . 在这里, main是一个函数, 它将一个函数作为参数,并在promise被解析/实现时调用它-我们称此方法resolve() The said function resolve() is implemented and provided by our Promise class. 所述函数resolve()由我们的Promise类实现并提供。

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

The basic feature of the then() method is to trigger/activate the provided callback function cb() with the promise value, once the promise fulfills. then()方法的基本功能是,一旦诺言实现,就用诺言值触发/激活提供的回调函数cb()

Taking these 2 things into account, we can rewire our Promise class. 考虑到这两点,我们可以重新连接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);
        });
    }
}

We can test our current code with a tester() function. 我们可以使用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>

This concludes our base. 到此为止我们的基础。 We can now implement chaining. 现在,我们可以实现链接。 The biggest problem we face is the the then() method must return a promise synchronously which will be resolved asynchronously . 我们面临的最大问题是, then()方法必须同步返回promise,该promise将异步解决。

We need to wait for the parent promise to resolve before we can resolve the next promise . 我们需要等待父承诺解决,然后才能解决下一个承诺 This implies that instead of adding cb() to parent promise , we must add the resolve() method of next promise which uses return value of cb() as its resolveValue . 这意味着,除了向父诺言中添加cb() ,我们还必须添加下一个诺言resolve()方法,该方法使用cb()返回值作为resolveValue

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

If this last bit confuses you, here are some pointers: 如果这最后一点使您感到困惑,那么这里有一些提示:

  • Promise constructor takes in a function main() as an argument Promise构造函数接受main()函数作为参数
  • main() takes a function resolve() as an argument main()将函数resolve()作为参数
    • resolve() is provided by the Promise constructor resolve()Promise构造函数提供
  • resolve() takes an argument of any type as the resolveValue resolve()任何类型的参数用作resolveValue

Demo 演示版

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>

Implementing for old zombie browsers (ES3 to above) 为旧的僵尸浏览器(ES3或更高版本)实施

I was using Promise in client js that figured out in old browsers this class doesn't exist. 我在客户端js中使用Promise ,但在旧的浏览器中却发现该类不存在。 So that I implemented one for them that have resolve and reject methods. 因此,我为他们实施了具有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;
}();

Solve it, when call sync in 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))
            })       
        }
    }
}

Or chain: 或连锁:

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;
        }
    }
}

Test: 测试:

(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