异步队列 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. 【翻译自mos文章】SYS_OP_C2C 导致的全表扫描(fts)/全索引扫描

    SYS_OP_C2C 导致的全表扫描(fts)/全索引扫描 參考原文: SYS_OP_C2C Causing Full Table/Index Scans (Doc ID 732666.1) 适用于: ...

  2. TDD(测试驱动开发)

    TDD(测试驱动开发)培训录 2014年我一直从事在敏捷实践咨询项目,这也是我颇有收获的一年,特别是咨询项目的每一点改变,不管是代码质量的提高,还是自组织团队的建设,都能让我们感到欣慰.涉及人的问题都 ...

  3. SQL点滴25—T-SQL面试语句,练练手

    原文:SQL点滴25-T-SQL面试语句,练练手 1. 用一条SQL语句查询出每门课都大于80分的学生姓名 name   kecheng    fenshu 张三     语文     81张三    ...

  4. Atitit.升级软件的稳定性---基于数据库实现持久化 循环队列 循环队列

    Atitit.升级软件的稳定性---基于数据库实现持久化  循环队列 环形队列 1. 前言::选型(马) 1 2. 实现java.util.queue接口 1 3. 当前指针的2个实现方式 1 1.1 ...

  5. 进击的Android注入术《二》

    继续 在<一>里,我把基本思路描写叙述了一遍,接下为我们先从注入開始入手. 注入 分类 我们平时所说的代码注入,主要静态和动态两种方式 静态注入,针对是可运行文件,比方平时我们改动ELF, ...

  6. 编写Javascript类库(jQuery版

    编写Javascript类库(jQuery版) - 进阶者系列 - 学习者系列文章 Posted on 2014-11-13 09:29 lzhdim 阅读(653) 评论(1) 编辑 收藏 本系列文 ...

  7. ZA7783:MIPI转LVDS/MIPI转RGB888/RGB转LVDS

    在消费类电子越来越白热化阶段.好多设计project师已经開始慢慢关注到成本控制,小金在这里就给大家带来一颗转接IC.希望能帮助贵公司控制成本.当然性能也是可靠的,已经好多产品设计了. 多多不吝赐教 ...

  8. Linux Shell 示例精解(第七章 gawk编程)转载

    第七章 gawk功能:gawk编程 7.1.1 数字和字符串常量     初始化和类型强制  在awk中,变量不需要定义就可以直接使用,使用一个变量就是对变量的定义.变量的类型可以试数字.字符串,或者 ...

  9. TodoList开发笔记 – Part Ⅲ

    本节开始对TodoList项目的客户端进行开发 一.初步了解JQuery 其实我在学校时有接触过一段时间的Web开发,虽然代码量不多也不复杂,但也已经感受到了各浏览器对Web各项标准的恶意,Web界对 ...

  10. 关于HTTP头标

    对于HTTP中的头字段,我表示真的好麻烦,特找来一段资料共享.希望能对大家有用. HTTP的头域包括通用头,请求头,响应头和实体头四个部分.每个头域由一个域名,冒号(:)和域值三部分组成.域名是大小写 ...