一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

如何更优雅的书写Javascript之告别回调地狱(上)

时间:2014-07-17 编辑:简简单单 来源:一聚教程网

最近太忙了,本来每周一篇的文章,一个月也没写一篇。哎!
接下来,打算对博客进行一番改版,可能会更换博客地址, 好好认真的写写文章,打算将来的博客内容将分下面几个方向:

博主自个写的技术文章
国外技术文章翻译
博主吐槽、感概、扯蛋类文章
但愿自己能做好。

什么是回调地狱?

PS: 某大牛说过,世上本没有多层回调嵌套,写的人多了也便有了。

下面我们举个例子

在使用jquery animat做多个动画效果的时候,可能很多的童鞋写出过如下的代码。

 代码如下 复制代码
// 在前一个动画执行完成后,紧接着执行下一个动画
$('xx').animate({xxxx}, function(){
    $('xx').animate({xx},function(){
        //do something
    },1000)
},1000)

由上面的代码可以看出,目前有2层的回调嵌套。
那我们思考下,如果我们有3个,4个或是更多的动画效果要紧接着执行,
我们是不是就发现,我陷入了无尽的回调地狱里去了呢?

可以访问这里看看我们平常会写出的一些回调 更多例子:js代码中的回调地狱

那么,到这里,大家看到了,大量的回调函数嵌套,
不仅仅让代码显得更加难看、臃肿,也让项目变得越来越不好维护。
所以,我们要想办法来避免这样子的代码出现在我们的项目中。

异步编程带来的好处和烦恼

javascript引擎是单线程的 但是借助异步编程,使得服务器端运行的NodeJs具备了高性能。
也具备了IO操作并行执行能力,这也使得很多童鞋可能写出很多回调嵌套的代码。
让我们的代码变得难看了。当然,这不是js语言本身的问题,就像开头写的:

世上本没有多层回调嵌套,写的人多了也便有了。

使用Promise来解决回调地狱问题

Promise是什么?

Promise是一种抽象对象。CommonJS定义了Promises/A规范。
如果想要具体了解点这里: 详细了解Promise是什么

Promise模式在任何时刻都处于以下三种状态之一:

未完成(unfulfilled)
已完成(resolved)
拒绝(rejected)。
在jquery的1.5以上版本的ajax就是基于promise实现的。 所以我们可以使用如下方式实现ajax请求:

 代码如下 复制代码

$.when(
    $.ajax('url'),
    $.ajax('url2')
)
.then(successCallback,failCallback);
//回调函数会在2个ajax都完成的情况下才会执行
注:$.ajax(‘url’) 返回的是一个Promise对象

如何实现一个Promise

接下来我们来实现一个Promise,我们就暂且称他为Deferred(跟jquery里保持一致)

 代码如下 复制代码

function Deferred() {
    this.doneCallbacks = [];
    this.failCallbacks = [];
}

Deferred.prototype.done = function(cb){
    this.doneCallbacks.push(cb);
    return this;
};

Deferred.prototype.fail = function(cb){
    this.failCallbacks.push(cb);
    return this;
};

Deferred.prototype.resolve = function(){
    var dcbs = this.doneCallbacks;
    for(var i=0,len=dcbs.length;i         dcbs[i]();
    }
    return this;
}

// 模拟$.when的实现
function when(){
    var aIdx = 0, aLen=arguments.length,
        dfd = new Deferred();

    function callback(){
        console.log("已执行了[ " + ++aIdx + " ]个方法");
        if(aIdx == aLen) {
            dfd.resolve(); // 改变Deferred对象的执行状态
        }
    }

    for(var i=0; i< aLen; i++){
        arguments[i].done(callback);
    }
    return dfd;
}

// 自定义异步执行的方法
var wait = function(intval){
    var dfd = new Deferred(); //在函数内部,新建一个Deferred对象
    var tasks = function(){
       console.log("耗时[ "+intval/1000+" ]秒的操作执行完毕!");
       dfd.resolve(); // 改变Deferred对象的执行状态
    };
   setTimeout(tasks,intval);
   return dfd; // 返回Deferred对象
};

// 调用测试
when(wait(5000),wait(10000)).done(function(){console.log('调用第一个done');}).done(function(){console.log('调用第二个done');});
console.log('over')

如上代码,原理即为:使用Deferred来保存回调函数和状态,等异步操作完成后,改变状态,触发相应回调函数。

使用Primose方式解决异步编程回调的一些问题

如果要使用Priomse方式来解决异步编程带来的回调嵌套问题,
那么我们势必需要将我们的所有包含异步操作的函数进行一层封装,
封装成Promise对象,我们才能使用类似:

$.when(promise1,promise2,....).then(...);
回到开头提到的动画的例子,我们可以这样子来实现:

 代码如下 复制代码

function animate(dis,time){
    var def = $.Deferred();
    $('.boll')
    .animate({
        left: dis+'px'
    },time, function(){
        def.resolve(time);
    });
    return def;
}

$('.boll').on('click',function(){
    $.when(
    animate(50,1000),
    animate(120,100),
    animate(200,500))
    .done(function(dd,tt,kk){
        console.log(dd,tt,kk)
    })
});


when里参数必须是Promise对象,when(xxx) 返回的也是一个Promise。
所以这必然增加了我们一些代码量,但是为了更好的可读性和可维护性,是值得的。
那么Promise介绍到这里,接下来的还有2篇文章,将使用另外2种方式来解决异步编程带来的回调嵌套问题。

热门栏目