移动web app开发必备 - Deferred 源码分析
姊妹篇 移动web app开发必备 - 异步队列 Deferred
在分析Deferred之前我觉得还是有必要把老套的设计模式给搬出来,便于理解源码!
观察者模式
观察者模式( 又叫发布者-订阅者模式 )应该是最常用的模式之一.
它定义了一种一对多的关系让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己。
使用观察者模式的好处:
- 支持简单的广播通信,自动通知所有已经订阅过的对象。
- 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性。
- 目标对象与观察者之间的抽象耦合关系能够单独扩展以及重用。
- 在这种模式中,有两类对象,分别是“观察者-Observer”和“目标对象-Subject”。
- 目标对象中保存着一份观察者的列表,当目标对象的状态发生改变的时候就主动向观察者发出通知(调用观察者提供的方法),从而建立一种发布/订阅的关系。
- 这一种发布/订阅的关系常用于实现事件、消息的处理系统。
观察者模式类图
参与者
- Subject(抽象主题)
- 能够知道它自己的观察者,若干观察者对象可能监视一个主题对象;
- 提供一个接口,用来附加和取消观察者对象;
- Observer(抽象观察者)
- 它为对象定义了一个(自我)更新的接口,并且当主题对象发生改变的时候能够被通知;
- ConcreteSubject(具体主题)
- 存储具体观察者感兴趣的状态;
- 当它的状态改变的时候,通知它所有的观察者对象;
- ConcreteObserver(具体观察者)
- 持有一个具体主题的引用;
- 存储和主题对象一致的状态,并且该能够保留该状态;
- 实现抽象观察者的Update接口,以便能够同主题对象的状态保持一致;
工作方式
- 当某个具体主题状态发生改变的时候,通知它的所有观察者对象,以便保证这些观察者对象的状态同它的状态保持一致;
- 当被告知具体主题对象发生改变,一个具体观察者对象可能会去查询主题对象,以便获得一个消息;该观察者使用这个消息来使它的状态同主题对象的状态保持一致;
观察者模式序列图
观察者对象初始化变化请求,它不会立即更新,直到通过主题对象的Notify方法来获得一个变化的通知。主题的Notify方法并非总是被主题对象调用,它也能够被观察者对象调用,或者完全被其他不同类型的对象调用。
Deferred怎样工作
- 可以创建一个Deferred对象直接,但你通常请求从一个数据源
- 数据源是一个函数,返回一个延迟对象
- 延迟对象提供了访问数据源的数据,允许你把成功回调函数/或失败函数其回调链
- 当数据源有结果,它将在延时对象上调用resolve(result)方法或者在失败的情况下调用reject(failure)方法,这导致延迟对象的回调链被释放了——这意味着每个链接链(回调或errback)被称为反过来,结果是输入到第一个回调,它的输出是输入到下一个回调(等等)
- 如果一个callback (or errback) 返回一个 Failure对象,那么下一个就是errback调用,是否就是callback调用
Deferred整体结构
Deferred的定义:
- Deferred是基于Promises/A,Promises是一种异步编程模型,通过一组API来规范化异步操作,这样也能够让异步操作的流程控制更加容易
- 由于Promises对于新手而言理解曲线还是比较陡峭的,这里循序渐进的给大家介绍,同时实现一个最简单的Promises/A代码
- Promises/A有个别名叫做“thenable”,就是“可以then”的。这里一个promise有三种状态:[默认、完成、失败],初始创建的时候是默认状态,状态只可以从默认变成完成,或者默认变成失败。一旦完成或者失败,状态就不能再变定义的接口
Deferred提供的API
var DeferredAPI = {
deferred : deferred,
all : all,
Deferred : Deferred,
DeferredList : DeferredList,
wrapResult : wrapResult,
wrapFailure : wrapFailure,
Failure : Failure
}
案例分析一
我们通过简单的demo来解析程序的执行流程
function asynchronous(delay,name) {
var d = new deferred.Deferred()
setTimeout(function() {
d.resolve('执行'+name)
}, delay || 1000);
return d
};
var d1 = new asynchronous(1000, 'd1');
d1.then(function(result){
alert(result) //结果是 执行d1
})
- 执行asynchronous方法,传入参数
- new deferred.Deferred() 用来构造一个Deferred对象
- setTimeout 模拟异步执行操作
- d.resolved表示该操作成功完成了
- 返回Deferred对象
- then 增加成功回调函数和失败回调函数到各自的队列中便捷方法,两个参数可以是数组或null
- setTimeout 执行完毕后出发resolved方法
- 执行then注册的回调函数
- 执行结束
源码实现
通过查看源码就能发现,其实Deferred.js的官方API不清晰,内部还有很多接口的实现没有说明
内部实现的处理器就2个模块,分别是:
- 处理单个异步 Deferred 类
- 处理多个异步 DeferredList 类
Deferred 类
new deferred.Deferred()
deferred.deferred()
二着是等同的,最终的实现是Deferred类,每次实例化一次就是一个新是上下文
Deferred在构造之后,相对的实例就具有了以下方法:
- cancel
- then
- fail
- both
- resolve
- reject
- pause
- unpause
- inspect
- thenReturn
- thenCall
- failReturn
- failCall
Deferred.then方法
来,订阅一个
Deferred.prototype.then = function (callback, errback) {
this.callbacks.push({callback: callback, errback: errback})
if (this.called) _run(this)
return this
}
可以直接传入二个回调函数,分别对应都 done|fail 二个状态的回调, 跟 $.jquery还是有区别,可以直接传入三个回调函数,分别对应done|fail|progress三个状态的回调
用this.callbakcs 存储具体观察者感兴趣的内容
Deferred.resolve方法
发布通知
Deferred.prototype.resolve = function(result) {
_startRun(this, result)
return this
}
代码中间有很多逻辑判断,我们暂且先跳过,看看最终的执行方法
function _run(d) {
if (d.running) return
var link, status, fn
if (d.pauseCount > 0) return while (d.callbacks.length > 0) {
link = d.callbacks.shift()
status = (d.result instanceof Failure) ? 'errback' : 'callback'
fn = link[status]
if (typeof fn !== 'function') continue
try {
d.running = true
d.result = fn(d.result)
d.running = false
if (d.result instanceof Deferred) {
d.pause()
_nest(d)
return
}
} catch (e) {
if (Deferred.consumeThrownExceptions) {
d.running = false
var f = new Failure(e)
f.source = f.source || status
d.result = f
if (d.verbose) {
console.warn('uncaught error in deferred ' + status + ': ' + e.message)
console.warn('Stack: ' + e.stack)
}
} else {
throw e
}
}
}
}
运行流程
- 其中d就是传入的异步对象了
- d.callbacks就是订阅收集的具体观察者感兴趣的内容,也就是callback or errback
- 匹配出正确的回调方法,执行
- d.result = fn(d.result) 传入回调函数需要的参数
- 单一的流程就执行完毕了
我们看看复杂点的构造
案例分析二
等待许多异步数据源
function asynchronous(delay,name) {
var d = new deferred.Deferred()
setTimeout(function() {
d.resolve('执行'+name)
}, delay || 1000);
return d
}; var d1 = new asynchronous(2000, 'd1');
var d2 = new asynchronous(3000, 'd2'); deferred.all([d1, d2]).then(function(result){
console.log(result) //["执行d1", "执行d2"]
}).thenCall(console.log(11111));
执行流程:
- 等待在一个列表的所有值的递延的对象,或者开始一群延迟操作和运行回调链当第一个成功还是失败了
- 等待d1, d2都加载完毕后接受到所有的values后,deferred.all运行作用域链,当然也一样可以提供成功或者失败
- 返回的result将会是数组格式的
convert ['a', 'b', 'c'] to 'abc'
或者传入配置参数:
fireOnFirstResult: true 只要返回第一个d1处理
fireOnFirstError: true 只返回第一个错误的处理
deferred.all([d1, d2],{fireOnFirstResult: true}).then(function(result){
console.log(result) //["执行d1"]
}).thenCall(console.log(11111));
最后看2个API没有给出的接口
dAll.failCall(console.error)
dAll.then(join).thenCall(console.log)
案例分析三
任意组合模式
function asynchronous(delay,name) {
var d = new deferred.Deferred()
setTimeout(function() {
d.resolve('执行'+name)
}, delay || 1000);
return d
}; var d1 = new asynchronous(2000, 'd1'); var d = d1.then(function(result1) {
var d2 = new asynchronous(200, 'd2');
return d2.then(function(result2) {
return result
})
}).then(function(data) {
console.log(data)
return data
})
执行流程:
- 一个回调函数的返回值被传递到下一个回调。
- 当一个回调返回一个延迟对象,原来的延迟将透明地等待其他接收它的值,然后运行它自己的回调链使用该值。我们把这种叫做嵌套。
总结:
- 现在我们可以用deferred对象做很多事了,返回它,将它传递给另一个函数,等。
- 随后的回调注册在deferred将收到返回的值从最内层的回调:result1+ result2。
- 用起来是不是很爽!
考虑这章拉的太长了,大家看的会有点小闷,所以下一节就开始队列的源码分析了
移动web app开发必备 - Deferred 源码分析的更多相关文章
- WEB前端开发学习:源码canvas 雪
WEB前端开发学习:源码canvas 雪 双旦节要到了,程序员们为了响应气氛,特别用代码制作了动态雪花,WEB前端开发学习的初学者们一起跟着案例做一遍吧! <!DOCTYPE html> ...
- 移动web app开发必备 - 异步队列 Deferred
背景 移动web app开发,异步代码是时常的事,比如有常见的异步操作: Ajax(XMLHttpRequest) Image Tag,Script Tag,iframe(原理类似) setTimeo ...
- jQuery.Deferred 源码分析
作者:禅楼望月(http://www.cnblogs.com/yaoyinglong ) 1 引子 观察者模式是我们日常开发中经常用的模式.这个模式由两个主要部分组成:发布者和观察者.通过观察者模式, ...
- client-go客户端自定义开发Kubernetes及源码分析
介绍 client-go 是一种能够与 Kubernetes 集群通信的客户端,通过它可以对 Kubernetes 集群中各资源类型进行 CRUD 操作,它有三大 client 类,分别为:Clien ...
- tornado高效开发必备之源码详解
前言:本博文重在tornado源码剖析,相信读者读完此文能够更加深入的了解tornado的运行机制,从而更加高效的使用tornado框架. 本文参考武sir博客地址:http://www.cnblog ...
- 移动web app开发必备 - zepto事件问题
问题描述: 项目在祖先元素上绑定了 touchstart,touchmove,touchend事件,用来处理全局性的事件,比如滑动翻页 正常状态下: 用户在子元素上有交互动作时,默认状态下都是会冒泡到 ...
- Android开发:TableFixHeaders源码分析
最近需要在android上的展示表格数据,在github上找到了TableFixHeaders(https://github.com/InQBarna/TableFixHeaders). 项目文件最主 ...
- odoo开发笔记--定时任务源码分析
场景描述: 处理思路: 参考文章: 定时任务相关: https://www.jianshu.com/p/ad48239f84d6 https://blog.csdn.net/M0relia/artic ...
- web app 开发必不可少的滑动插件 Flipsnap
flipsnap.js一个轻量级的滑动效果JS开发库,仅有8k大小(压缩版),包含了10种滑动方式,是web app开发必备的js库,除了兼容主流的智能手机浏览器(iossafari,android, ...
随机推荐
- Linux内核补丁批量自动下载工具
Linux kernel官网cgit工具不支持按变更代码进行补丁搜索,想到个办法就是把补丁都抓下来,这样可以在本地搜索.花了2个小时写了个小工具,话不多说,直接看效果: E:\docs\TOOLS\p ...
- Python 端口,IP扫描
Python Socket函数 (1)TCP发送数据时,已建立好连接,不需要指定地址.UDP是面向无连接,每次分别指定发给谁. (2)s.bind(address) 将套接字绑定到地址,在AF_INE ...
- flash跨域访问,crossdomain.xml,error #2048
最近遇到了flash的万年老梗,跨域访问的问题.之前一直没有处理过这类问题,是因为做项目的时候别人已经处理好了.真到自己遇到的时候,还是很费脑筋的. 1遇到的问题 客户端发布到网页的时候,socket ...
- windows环境下搭建vue+webpack的开发环境
前段时间一直在断断续续的看vue的官方文档,后来就慢慢的学习搭建vue的开发环境,已经有将近两周了,每到最后一步的时候就会报错,搞的我好郁闷,搁置了好几天,今天又接着搞vue的开发环境,终于成功了.我 ...
- Ejabberd导入到eclipse
ejabberd 在eclipse(erlide)中的配置.调试.运行 最近在折腾ejabberd,将ejabberd项目配置到eclipse中进行编译.调试等,现在将过程记下来,希望能帮助到需要 ...
- C#窗体中读取修改xml文件
由于之前没有操作过xml文件,尤其是在窗体中操作xml,脑子一直转不动,而且很抵制去做这个功能,终于还是突破了自己通过查询资料完成了这个功能,在此记录一下自己的成果. 功能说明:程序中存在的xml文件 ...
- 数字对象NSNumber
//将int类型转化成对象 ; NSNumber *numberString = [NSNumber numberWithInt:number]; //对象是可以放入数组的 NSArray *arra ...
- 移动前端不得不了解的html5 head 头标签
本文主要内容来自一丝的常用的 HTML 头部标签和百度FEX的HTML head 头标签. 移动端的工作已经越来越成为前端工作的重要内容,除了平常的项目开发,HTML 头部标签功能,特别是meta标签 ...
- bat获取所有的参数
bat默认只能获取到1-9个参数,分别用%1 %2 ... %9引用,如果传给bat的参数大于9个,就必须用shift. 工作需要,要写个bat脚本,获取所有的参数,再将所有的参数传给Java,代码如 ...
- SQL初步知识点
varchar(n) 长度为 n 个字节的可变长度且非 Unicode 的字符数据.n 必须是一个介于 1 和 8,000 之间的数值.存储大小为输入数据的字节的实际长度,而不是 n 个字节. nva ...