3

最近看了一篇关于Promise内部实现原理的文章Javascript in wicked detail。作者从简明的例子入手,一步一步的构建健壮的Promise实现。我就拿作者文中的代码实例梳理下文章的核心内容。

大家一定看到过嵌套很深回调函数,那么如何在保证代码流程才能将这些纵向嵌套的代码变成横向偏平的呢?

    doSomething(function(value) {
        console.log('Got a value:' + value);
    })

to this

    doSomething().then(function(value) {
        console.log('Got a value:' + value);
    })

那么我们就应该在定义doSomething函数的时候做出相应的变化

    function doSomething(callback) {
        var value = 42;
        callback(value);
    }

to this

    function doSomething() {
        return {
            then: function(callback) {
                var value = 42;
                callback(42);
            }
        }
    }

Defining the Promise type

首先来看一段定义简单Promise构造函数的代码:

    function Promise(fn) {
        var callback = null;
        this.then = function(cb) {
            callback = cb;
        }
        
        function resolve(value) {
            callback(value)
        }
        
        fn(resolve);
    }

然后重写doSomething()函数:

    function doSomething() {
        return new Promise(function(resolve) {
            var value = 42;
            resolve(value);
        })
    }

重新定义后的doSomething()函数执行后返回得到一个promise实例,实例上有then()方法,可以接受回调函数。

    doSomething().then(function(value) {
        console.log(value);
    })

但是上面的代码会报错(callback is undefined),是因为:resolve中的callback要早于then()方法中的callback的赋值操作

那么对Promise构造函数稍微处理下,把同步的代码使用setTimeout来hack下,改变代码的执行顺序,使得resolve函数中的callbackvalue进行处理前被赋值了。

    function Promise(fn) {
        var callback = null;
        this.then = function(cb) {
            callback = cb;
        }
        
        function resolve(value) {
            setTimeout(function() {
                callback(value);
            }, 1)
        }
        fn(resolve);
    }

这里通过setTimeout异步函数改变了代码执行的顺序,确保callback被调用前已经被赋值成cb

重新调用:

    doSomething().then(function(value) {
        console.log(value);
    })
    // 42
    //正常执行。

但是定义Promise构造函数的代码还是有问题的,因为如果仅仅是调用then()方法而注入回调的话,内部的callback仍然是null。同样不能正常的执行。

别急,慢慢来。

Promises have state

事实上Promise是有状态的:

  • pending

  • resolved

  • rejected

pending => resolved 或者 pending => rejected。状态一旦发生改变,不可逆。接下来,让我们在Promise的构造函数里面加入state,使用state来控制整个代码流程。

    function Promise(fn) {
        var state = 'pending', 
            value, 
            deferred;
        
        function resolve(newValue) {
            state = 'resolved';
            value = newValue;
            
            if(deferred) {
                handle(deferred);
            }
        }
        
        function handle(onResolved) {
            if(state === 'pending') {
                deferred = onResolved;
                return ;
            }
            
            onResolved(value);
        }
        
        this.then = function(onResolved) {
            handle(onResolved);
        }
        
        fn(resolve);
    }

代码变的比之前更加复杂。但是现在使用state来控制代码的流程。then()方法resolve()方法将控制权交给了新的方法handle(),由handle()方法来根据state的值进行流程操作:

  • 如果statepending状态,即在resolve()之前调用了then()方法,那么会将onResolved回调赋值给一个deferred延迟对象deferred对象将这个回调保存起来,稍后当resolve()调用时,pending状态变为resolved,并调用deferred对象

  • 如果在then()方法前调用resolve()方法,pending状态变为resolved,然后调用then()里面注入的回调onResolved.

通过以上的代码,promise可以任意次数的调用then()方法:

    var promise = doSomething();
    
    promise.then(function(value) {
        console.log('Got a value:', value);
    });
    // 42
    
    promise.then(function(value) {
        console.log('Got the some value again:', value); 
    });
    //42

但是这样的Promise构造函数还是有问题的,大家可以想象下,在调用resolve()方法前,调用了很多次的then()方法,那么只有最后一个then()方法里面注入的callback才会有用。解决这个问题的方法就是维持一个deferreds队列,去保存每次then()方法注入的回调函数。

Chaining Promises

下面的代码是最普通不过的promise链式调用:

    getSomeData()
    .then(filterTheData)
    .then(processTheData)
    .then(displayTheData)

getSomeData()方法调用后会返回一个promise对象,这样便可以调用then()方法,同样这第一个then()方法调用后也会返回一个promise对象。这样才能继续调用then()方法。

then()方法总是返回一个promise。

接下来在代码中加以实现:

    function Promise(fn) {
        var state = 'pending',
            value,
            deferred = null;
            
        function resolve(newValue) {
            state = 'resolved';
            value = newValue;
            
            if(deferred) {
                handle(deferred);
            }
        }
        
        function handle(handler) {
            if(state == 'pending') {
                deferred = handler;
                return;
            }
            
            if(!handler.onResolved) {
                handler.resolve(value);
                return;
            }
            
            var ret = handler.onResolved(value);
            handler.resolve(ret);
        }
        
        this.then = function(onResolved) {
            return new Promise(function(resolve) {
                handle({
                    onResolved: onResolved,
                    resolve: resolve
                });
            });
        };
        
        fn(resolve);
    }

在这次的代码中,调用then()方法后会返回一个新的生成的promise对象。它具有then()方法,可以继续调用then(),并返回一个新生成的promise对象。如此继续进行下去。这就实现了Promise链式调用。

再来看看具体的代码实现:
resolve()方法没什么变化,但是handle()方法接收一个handler对象。handler对象有2个属性,一个为onResolvedthen()方法里面注入的回调函数,用来对传入的上一个promise传递过来的值进行处理;另一个为resolvePromise构造函数内部定义的resolve()方法,用来改变Promise状态以及value值。

具体分析下handle()函数:

    function handle(handler) {
        if(state === 'pending') {
            deferred = handler;
            return;
        }
        
        if(!handler.onResolved) {
            handler.resolve(value);
            return;
        }
        
        var ret = handler.onResolved(value);
        handler.resolve(ret);
    }

每次调用then()方法新建一个promise对象过程当中,handle({onResolved: onResolved, resolve: resolve})resolve属性始终是获得的定义过程中对外部resolve方法的引用。即上一次的promise中定义的resolve.

当then()方法里面注入回调函数时,调用onResolved方法并获得返回值ret,传入resolve方法,改变state的值以及更改promise中需要继续传递下去的值。如果onResolved方法中会返回处理过的值,那么下一个promise能拿到这个值,如果onResolved没有返回,传入下一个promise的为undefined**

    doSomething().then(function(result) {
        console.log('First result', result);
        return 88;
    }).then(function(secondResult) {
        console.log('second result', secondResult);
    })
    
    //the output is 
    //
    //First result 42
    //Second result 88
    
    doSomething().then(function(result) {
        console.log('First result', result);
    }).then(function(secondResult) {
        console.log('Second result', secondResult);
    })
    
    //now the output is
    
    //First result 42
    //Second result undefined
    
当then()没有注入回调函数时,仍然会调用resolve方法,改变state的值,以及获取上一个promise传递过来的值,并将值传递给下一个promise。
    doSomething().then().then(function(result) {
        console.log('Got a result', result);
    });
    
    //the output is 
    //
    //Got a result 42

主要是得益于handle()方法中,调用resolve方法获取从上一个promise得到的value以及作为传入下一个promisevalue:

    if(!handler.onResolved) {
        handler.resolve(value);
        return;
    }
再每次调用then()方法的过程都会新建一个pending状态的promise,并通过resolve方法改变状态,如果then()方法中注入了回调函数,并返回了值,那么这个值会一直传递下去,如果没有注入回调函数,resolve方法会获取上一个promise传递过来的值,并作为传入下一个promise的值。即then()方法注入的回调函数是可选的。

通过再次对Promise构造函数的加强,完成了promise链式调用的功能。


对于reject的部分过2天加上。


苹果小萝卜
5.1k 声望356 粉丝

Github: [链接]