微信号:FrontDev

介绍:分享 Web 前端相关的技术文章、工具资源、精选课程、热点资讯

从Promise的Then说起

2016-04-02 20:06 前端大全

(点击上方,可快速关注)


作者:AlloyTeam 

网址:http://www.alloyteam.com/2016/03/from-the-promise-then-said-on/


Promise让代码变得更人性化


曾经我一直在思考,为什么代码会比较难读。后来发现,我们平时要阅读的所有媒体:报纸、书、新闻,我们在阅读的时候,都是从上到下一直读下去的,然而,我们的在读代码的时候,经常要跳着去读,这种阅读方式其实是反人类的,如果我们能在读代码的时候,也可以从上往下一直读下去,那么,代码就会变得可读性提高很多。


对比JS中,callback是让我们跳来跳去读代码最大的罪魁祸首,它让我们的流程变得混乱,Promise正是为了解决这一问题而产生的。我们来对比一下这个过程


var render = function(data){

};

 

var getData = function(callback){

 

    $.ajax({

        success: function(){

               callback(data);

        }

    });

};

 

var init = function(){

    getData(function(data){

         render(data);

 

         getData(function(data){

             render(data);

         });

    });

};

 

init();


使用Promise之后


var init = function(){

    getData({

 

    }).then(function(data){

 

       render(data);

       return getData({});

    }).then(function(data){

 

       render(data);

    });

};


很明显看出,代码就变成线性的了,逻辑也变得更加清晰可读


Promise流程再优化


promse出来之后,大家都有很多的想法,在Promise之上再封装一层,使用异步流程更清晰可读。下面是Abstract-fence 2.0(开发中)的一种解决方案(规范)


Abstract-fence中,function会被分解为多个task


Model.task('getData', function(scope, next,{$, util}){

      $.ajax({

          success: function(data){

              next();

          }

      });

});

 

Model.task('render', ['getData'], function(scope){

      var data = scope.data;

      // 使用data进行渲染

});

Model.task('init', [render].then(render));

 

Model.runWorkflow(init);


其中, init是task render执行后再执行render, 而render任务又是由getData任务执行后再渲染组成,其中每个task的定义function的参数使用依赖注入传递,全局属性使用{}包裹


但是在使用Promise.all的过程中,遇到了一个Promise奇怪的问题


Array.prototype.then与Promise.all


很简单的一段代码


var p = new Promise(function(rs, rj){

    setTimeout(rs, 1000);

});

 

Promise.all([p]).then(function(){

    console.log(2);

});

 

console.log(1);


毫无疑问,这段代码在浏览器运行会先打印1,然后再输出2 但如果在前面增加对then方法的定义,如下代码


Array.prototype.then = function(){

};

var p = new Promise(function(rs, rj){

    setTimeout(rs, 1000);

});

 

Promise.all([p]).then(function(){

    console.log(2);

});

 

console.log(1);


那么这段代码只会打印出1, 2却永远不会运行


查了很多资料,确认promise.all的参数只能接收数组(类数组) 


比如如下代码就会报错


var p = new Promise(function(rs){});

 

Promise.all(p); // 报错

 

Promise.all([p]); // ok


所以,Promise.all接收一个Iterator可遍历对象


对数组的prototype.then定义为什么会影响到Promise的行为呢?


Promise A+规范


Promise A+规范看起来还是有点绕,这里省略掉一些具体的实现细节,将Promise A+更直白的阐述如下


1. Promise then方法需要return一个新的Promise出来,如下


.then = function(rsFunc, rjFunc){

 

    var promise2 = new Promise();

    return promise2;

};


2. 如果promise本身状态变更到fulfilled之后,调用rsFunc,rsFunc的解析值x, 与新的promise2进行promise的解析过程[[Resolve]](promise2, x), x的取值不同,有不同的情况


3. 若x为一个promise,则x完成的最后,再fufill promise2, 对应如下代码


new Promise(function(rs, rj){

  rs();

 

}).then(function(data){

    // 对应于x的返回值

    return new Promise(rs, rj){

    });

});


4. 若x为一个对象或者函数,如果有then方法,将会执行then方法,then方法this指向x本身,如下


new Promise(function(rs, rj){

 

   rs();

}).then(function(data){

    // 对应于x的返回值

    return {

          a: 1,

          then: function(rs, rj){

                console.log(this.a);

                rs({a:2});

          }

    };

}).then(function(data){

   console.log(data.a);

});


5. 如果x没有then方法,那么,x将会做为值来 满足 promise2


new Promise(function(rs, rj){

 

   rs();

}).then(function(data){

    // 对应于x的返回值

    return {

          a: 1

    };

}).then(function(data){

   console.log(data.a);

});


Promise A+给出了一些具体的执行细节,保证了then的顺序执行,但在规范中,并未提到Promise.all方法的执行方式


为此,查看bluebird的Promise.all实现方法


BlueBird关于Promise.all实现方法解析


首先,promise中引用promise_array代码如下(已略去一些无关代码)


var INTERNAL = function(){};

var apiRejection = function(msg) {

    return Promise.reject(new TypeError(msg));

};

function Proxyable() {}

 

var tryConvertToPromise = require("./thenables")(Promise, INTERNAL);

var PromiseArray =

    require("./promise_array")(Promise, INTERNAL,

                               tryConvertToPromise, apiRejection, Proxyable);


promise.all的实现也很简单


Promise.all = function (promises) {

    return new PromiseArray(promises).promise();

};


可见,具体的细节在promise_array中的实现


function PromiseArray(values) {

    var promise = this._promise = new Promise(INTERNAL);

    if (values instanceof Promise) {

        promise._propagateFrom(values, 3);

    }

    promise._setOnCancel(this);

 

    this._values = values;

    this._length = 0;

    this._totalResolved = 0;

 

    // 初始化

    this._init(undefined, -2);

}


PromiseArray的构造方法中,将参数赋值给this._values,待_init方法中使用


PromiseArray.prototype._init = function init(_, resolveValueIfEmpty) {

    var values = tryConvertToPromise(this._values, this._promise);

 

    // 如果values可以转化为promise对象,那么根据不同的状态,会提前return

    if (values instanceof Promise) {

        values = values._target();

        var bitField = values._bitField;

        ;

        this._values = values;

 

        // 这个状态是pending的状态

        if (((bitField & 50397184) === 0)) {

            this._promise._setAsyncGuaranteed();

 

            // Promise,将会等Promise对象本身状态改变后再次

            return values._then(

                init,

                this._reject,

                undefined,

                this,

                resolveValueIfEmpty

           );

 

        // FULFILLED, 并没有提前return, 继续进行

        } else if (((bitField & 33554432) !== 0)) {

            values = values._value();

 

        // rejected的状态,提前终止

        } else if (((bitField & 16777216) !== 0)) {

            return this._reject(values._reason());

        } else {

            return this._cancel();

        }

    }

    values = util.asArray(values);

    if (values === null) {

        var err = apiRejection(

            "expecting an array or an iterable object but got " + util.classString(values)).reason();

        this._promise._rejectCallback(err, false);

        return;

    }

 

    if (values.length === 0) {

        if (resolveValueIfEmpty === -5) {

            this._resolveEmptyArray();

        }

        else {

            this._resolve(toResolutionValue(resolveValueIfEmpty));

        }

        return;

    }

    this._iterate(values);

};


init总结为几步


1.尝试转换参数为Promise对象


2.如果转换成功,那么检查Promise对象的状态


1. Pending,等待Promise


2. fulfilled, 换取返回值,继续进行


3. Rejected 终止,返回原因


4. 其他, 终止


上面的代码可以看出,一旦数组的具有then方法,就可被tryConvertToPromise方法转换为一个Promise对象,如果then方法未实现promise规范,那么Promise对象就会处于Pending的状态,Promise.all方法永远就不会达到fulfilled的条件,问题也就明白了


【今日微信公号推荐↓】


更多推荐请看值得关注的技术和设计公众号


其中推荐了包括技术设计极客 和 IT相亲相关的热门公众号。技术涵盖:Python、Web前端、Java、安卓、iOS、PHP、C/C++、.NET、Linux、数据库、运维、大数据、算法、IT职场等。点击《值得关注的技术和设计公众号》,发现精彩!




音乐节详情,请点阅读原文

 
前端大全 更多文章 详解Javascript中的Object对象 结合个人经历总结的前端入门方法 前端不为人知的一面–前端冷知识集锦 一份优秀的前端开发工程师简历是怎么样的? 浅谈Web缓存
猜您喜欢 第4章 类和接口 谈谈Android中的观察者模式 为什么你的决定总出错?--20种常见思维误区 数据分析一定要避免辛普森悖论 京东618智能卖场:个性化技术在大促会场上的实践