异步队列 Deferred

背景:

移动web app开发,异步代码是时常的事,比如有常见的异步操作:

  • Ajax(XMLHttpRequest)
  • Image Tag,Script Tag,iframe(原理类似)
  • setTimeout/setInterval
  • CSS3 Transition/Animation
  • HTML5 Web Database
  • postMessage
  • Web Workers
  • Web Sockets
  • and more…

后面几个是CSS3 HML5加入的新API.这些接口都是会产生异步的操作

比如本人的一个phonegap项目,操作HTML5本地数据库(HTML5 Web Database)就是一个异步的过程,如果同时执行多个查询,势必同步代码要等待数据查询结束后调用

附项目源码:执行多次异步查询

/**

  * 初始化操作
  * @return
  */
 proto.initProcess = function(){
     var self = this,
         prev = null ,
         curr = null ,
         next = null ;
     debug.group("start of init process");
     var idx = self.chapterIndex;
     debug.info("PageBase: 执行初始化之前的操作!");
     self.initProcessBefore();
 
     if(idx == 0){
         debug.info("PageBase: 初始化入口点从第一章开始进入");
         debug.info("PageBase: 解析器解析第一章数据!");
         curr = self.process(self.chapters[idx]);
         curr.then(function(pages){
             debug.info(self.format("PageBase: 第一章数据解析完成,解析页面数为{0}" , pages.length));
             self.cPages = pages;
             if(self.isChangeFont){
               self.idx = Math.ceil((pages.length - 1) * self.idx);                 
             }
 
             self.cPages.idx = idx;
 
             /////////////////////////////////////////////////
             //
             // 2013.1.10修改
             //   如果只有一个章节的情况下
             //
             if(1 === self.chapters.length){
               deferred.all([curr]).then(self.steup.bind(self));  
             }else{
               debug.info("PageBase:解析器解析后一章数据!");
               next = self.loadNextData(idx + 1);
               next.then(function(args){
                   debug.info(self.format("PageBase: 后一章数据解析完成,解析页面数为{0}" , args.pages.length));
                   self.nPages = args.pages;
                   self.nPages.idx = idx + args.index;
                   debug.info(self.format("PageBase: 初始化数据解析完成, 当章索引{0} 当章页数{1} 下章索引{2}  下章页数{3}"
                           , self.cPages.idx , self.cPages.length , self.nPages.idx , self.nPages.length));
              
                   debug.info("PageBase: 初始化数据解析完成,即将生成结构操作!");
               });
               deferred.all([curr , next]).then(self.steup.bind(self));  
             }
 
         });
     }else if(idx == self.chapters.length -1){
         debug.info("PageBase: 初始化入口点从最后一章开始进入");
         debug.info("PageBase:解析器解析最后一章数据!");
         prev = self.loadPrevData(idx - 1);
         prev.then(function(args){
             self.pPages = args.pages;
             self.pPages.idx = args.index + 1;
             debug.info(self.format("PageBase: 最后一章的前一章数据解析完成,解析页面数为{0}" , args.pages.length));
             curr = self.process(self.chapters[idx]);
             curr.then(function(pages , data){
                 if(self.isChangeFont){
                   self.idx = Math.ceil((pages.length - 1) * self.idx);                 
                 }
                 self.cPages = pages ;
                 self.cPages.idx = idx;
                 debug.info(self.format("PageBase: 最后一章数据解析完成,解析页面数为{0}" , pages.length));
                 debug.info(self.format("PageBase: 初始化数据解析完成, 前章索引{0} 前章页数{1} 当章索引{2} 当章页数{3} "
                         , self.pPages.idx , self.pPages.length , self.cPages.idx , self.cPages.length ));
            
                 debug.info("PageBase: 初始化数据解析完成,即将生成结构操作!");
             });
             deferred.all([prev , curr]).then(self.steup.bind(self));
         });
     }else{
         debug.info("PageBase: 初始化入口点从中间章开始进入");
         prev = self.loadPrevData(idx - 1);
         debug.info("PageBase:解析器解析中间章的前一章数据!");
         prev.then(function(args){
             self.pPages = args.pages ;
             self.pPages.idx = args.index;
             debug.info(self.format("PageBase: 中间章前一章数据解析完成,解析页面数为{0}" , args.pages.length));
             debug.info("PageBase:解析器解析中间章数据!");
             curr = self.process(self.chapters[idx]);
             curr.then(function(pages , data){
                 if(self.isChangeFont){
                     self.idx = Math.ceil((pages.length) * self.idx);
                     // console.log("spages.length - 1",pages.length)     
                     // console.log("self.idx",self.idx)            
                 }
                 self.cPages = pages ;
                 self.cPages.idx = idx;
                 debug.info(self.format("PageBase: 中间章数据解析完成,解析页面数为{0}" ,pages.length));
                 debug.info("PageBase:解析器解析中间章的后一章数据!");
                 next = self.loadNextData(idx + 1);
                 next.then(function(args){
                     self.nPages = args.pages ;
                     self.nPages.idx = idx + args.index;
                     debug.info(self.format("PageBase: 中间章后一章数据解析完成,解析页面数为{0}" , args.pages.length));
                     debug.info(self.format("PageBase: 初始化数据解析完成, 前章索引{0} 前章页数{1} 当章索引{2} 当章页数{3} 下章索引{4}  下章页数{5}"
                         , self.pPages.idx , self.pPages.length , self.cPages.idx , self.cPages.length , self.nPages.idx , self.nPages.length));
                     debug.info("PageBase: 初始化数据解析完成,即将生成结构操作!")
                 });
                 deferred.all([prev , curr , next]).then(self.steup.bind(self)); 
             });
         });
    }

如何组织代码

但是对于异步+回调的模式,当需要对一系列异步操作进行流程控制的时候似乎必然会面临着回调嵌套。因此怎么把异步操作“拉平”,用更好的方法去优化异步编程的体验,同时也写出更健壮的异步代码,是这两年来前端圈子里很火的话题。

代表的

  1. 消息驱动——代表:@朴灵 的EventProxy
  2. Promise模式——代表:CommonJS PromisesjQueryDojo
  3. 二次编译——代表:@老赵 的Jscex
  4. jQuery 是唯一的实现了这种 when 方法的库。其他的 promises 库,例如  QDojo, 和  when 依照  Promises/B spec 实现了 when 方法, 但是并没有实现注释者提及的 when 方法。但是,Q 库有一个   all方法,when.js 也有一个  parallel方法,与上面的 jQuery.when 方法作用一样,只是它们接受一个数组类型的参数,而不是任意数量的参数。

回顾Jquery Deferred

  • 从1.5版本开始,jQuery加入了Deferred功能,让事件处理队列更加的完善。并用 这个机制重写了Ajax模块。虽然还没轮到Ajax,但是接下来的事件处理函数中牵扯到了 这个机制
  • Deferred把回调函数注册到一个队列中,统一管理,并且可以同步或者异步地调用 这些函数。jQuery.Deferred()用来构造一个Deferred对象。该对象有状态值,共有三种: Rejected, Resolved和初始状态。其中Resolved表示该操作成功完成了,而Rejected 则表示出现了错误,调用失败。Deferred对象的主要成员如下:
  • done(callback): 注册一个callback函数,当状态为resolved时被调用。
  • fail(callback): 注册一个callback函数,当状态为rejected时被调用。
  • always(callback): 注册一个callback函数,无论是resolved或者rejected都会被 调用。
  • then(successCallback, failureCallback): 同时传入成功和失败的回调函数。
  • pipe(successFilter, failureFilter): 在调用成功和失败的回调函数前先调用pipe 指定的函数。算是一种管道机制,拦截了函数调用。
  • resolve(args): 把状态设置为Resolved。
  • reject(args): 把状态设置为Rejected。
  • promse(): 返回的是一个不完整的Deferred的接口,没有resolve和reject。即不能 修改Deferred对象的状态。可以看作是一种只读视图。这是为了不让外部函数提早触发 回调函数。比如$.ajax在1.5版本后不再返回XMLHttpRequest,而是返回一个封装了 XMLHttpRequest和Deferred对象接口的object。其中Deferred部分就是promise()得到 的,这样不让外部函数调用resolve和reject,防止在ajax完成前触发回调函数。把这 两个函数的调用权限保留给ajax内部。

deferred-js

本人在项目中使用 Promise/A 规范实现的 deferred-js , 比较简单轻巧.

如何使用?

API:

var DeferredAPI = {
    deferred     : deferred,
    all          : all,
    Deferred     : Deferred,
    DeferredList : DeferredList,
    wrapResult   : wrapResult,
    wrapFailure  : wrapFailure,
    Failure      : Failure
}

最简单常用的案例

           //Deferred对象创建
var d = new deferred.Deferred() //添加一个回调到递延的回调链
d.then(function(result) {
console.log('Hello ' + result)
return result
}) //等待回调后触发
d.resolve('World')

每个链接在一个回调链可以是两个函数,代表一个成功,一个失败

只有一个成功回调

d.then(function(result) {
// 自己的代码
return result
})

失败回调

d.fail(function(failure) {
// optionally do something useful with failure.value()
return failure
});

添加一个成功方法和一个失败方法

d.then(function(result) {
// do something useful with the result
return result
}, function(failure) {
// optionally do something useful with failure.value()
return failure
})

不管回调成功或者失败都执行同一份代码

d.both(function(result) {
// in the case of failure, result is a Failure
// do something in either case
return result
})

如果许多异步在操作,比如提供的案例,在要执行HTML5数据库N次后,如何操作呢?

请仔细对照下案例中的

 deferred.all([prev , curr , next]).then(self.steup.bind(self));  

all的方法等待所有的延时队列加载完毕后,才执行后续代码

使用起来很方便,很精简没有那么多复杂的概念

使用教程之后,下一节附源码的实现

 
 

异步队列 Deferred的更多相关文章

  1. 移动web app开发必备 - 异步队列 Deferred

    背景 移动web app开发,异步代码是时常的事,比如有常见的异步操作: Ajax(XMLHttpRequest) Image Tag,Script Tag,iframe(原理类似) setTimeo ...

  2. jQuery源码分析(九) 异步队列模块 Deferred 详解

    deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...

  3. jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解

    异步队列用于实现异步任务和回调函数的解耦,为ajax模块.队列模块.ready事件提供基础功能,包含三个部分:Query.Callbacks(flags).jQuery.Deferred(funct) ...

  4. .Net中的并行编程-4.实现高性能异步队列

    上文<.Net中的并行编程-3.ConcurrentQueue实现与分析>分析了ConcurrentQueue的实现,本章就基于ConcurrentQueue实现一个高性能的异步队列,该队 ...

  5. jquery ajax 对异步队列defer与XMLHttprequest.onload的依赖

    ajax 对异步队列defer与XMLHttprequest.onload的依赖

  6. [js高手之路]javascript腾讯面试题学习封装一个简易的异步队列

    这道js的面试题,是这样的,页面上有一个按钮,一个ul,点击按钮的时候,每隔1秒钟向ul的后面追加一个li, 一共追加10个,li的内容从0开始技术( 0, 1, 2, ....9 ),首先我们用闭包 ...

  7. .Net中的并行编程-7.基于BlockingCollection实现高性能异步队列

    三年前写过基于ConcurrentQueue的异步队列,今天在整理代码的时候发现当时另外一种实现方式-使用BlockingCollection实现,这种方式目前依然在实际项目中使用.关于Blockin ...

  8. 基于异步队列的生产者消费者C#并发设计

    继上文<<基于阻塞队列的生产者消费者C#并发设计>>的并发队列版本的并发设计,原文code是基于<<.Net中的并行编程-4.实现高性能异步队列>>修改 ...

  9. js异步队列之理解

    起因 最近看到一篇关于js异步执行顺序的解答,觉得有所收获,遂记录下来. marcotask和microtask js中异步队列可以分为两类,marcotask队列和microtask队列, marc ...

随机推荐

  1. Python - 字符串的替换(interpolation) 具体解释

    字符串的插值(interpolation) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/27054263 字符串的替换 ...

  2. Microsoft .NET Pet Shop 简介

    最初研究 .NET Pet Shop 的目的是用 Microsoft .NET 实现 Sun 主要的 J2EE 蓝图应用程序 Sun Java Pet Store 同样的应用程序功能. 根据用 .NE ...

  3. 日志分析工具-ApexSQL介绍

    原文:日志分析工具-ApexSQL介绍 使用场景:业务数据异常变化,通过代码分析不出来的时候,迫不得已需要通过日志来分析 下载地址:http://www.apexsql.com/Download.as ...

  4. CentOS下JAVA WEB 环境搭建

    首先介绍下我的软件环境.虚拟机Vmware9.0(已经汉化),CentOS6.4(选择安装语言为简体中文),xshell4.0(强大的安全终端模拟软件),xftp4.0(FTP工具). 方便大家环境搭 ...

  5. ubuntu下java和tomcat安装配置

    oracle下载jdk-7u51-linux-i586.tar.gz sudo cp Downloads/jdk-7u51-linux-i586.tar.gz /usr/local/java //拷贝 ...

  6. windows2008 配置安装FTP服务器

    windows2008 配置安装FTP服务器 今天在服务器上开了IIS7,但是要求把一个附件文件夹拷贝到根目录下面,这个附件文件夹有2G多大小,直接用远程桌面映射,一直都拷贝不成功,而且本地缓存越来越 ...

  7. Jqury笔记

    1.  --------------- -var aa = new Array(); aa.push(1); alert(aa[0]); var aa=[];也表示一个数组: ------------ ...

  8. T4模板demo

    T4模板_根据DB生成实体类   为了减少重复劳动,可以通过T4读取数据库表结构,生成实体类,用下面的实例测试了一下 1.首先创建一个项目,并添加文本模板: 2.添加 文本模板: 3.向T4文本模板文 ...

  9. Ninject是一款.Net平台下的开源依赖注入框架

    Ninject是一款.Net平台下的开源依赖注入框架.按照官方说法,它快如闪电.超级轻量,且充分利用了.Net的最新语法,使用Lambda表达式代替Xml文件完成类型绑定.Ninject结构精巧,功能 ...

  10. SQLSERVER 数据库性能的的基本 MVC + EF + Bootstrap 2 权限管理

    SQLSERVER 数据库性能的基本 很久没有写文章了,在系统正式上线之前,DBA一般都要测试一下服务器的性能 比如你有很多的服务器,有些做web服务器,有些做缓存服务器,有些做文件服务器,有些做数据 ...