Created by xiaolong / @zodiac-xl
Javascript语言的执行环境是"单线程"(single thread)。一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
为了解决阻塞,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。
经常的我们用callback实现异步,你还知道多少异步方案呢?
doAsync1(function () { doAsync2(function () { doAsync3(function () { doAsync4(function () { }) }) })
优点:简单、容易理解和部署
缺点:不利于代码阅读和维护,各部分高度耦合,流程混乱,且每个任务只能指定一个回调函数
f1.on('done1', f2); f1.on('done2', f3); function f1(){ // f1的任务代码 f1.trigger('done1'); f1.trigger('done2'); }
执行晚f1的代码后触发f1的done事件,调用f2
优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。
缺点:整个程序都要变成事件驱动型,运行流程会变得很不清晰。
上一节的"事件"可以理解成"信号"。
假定,存在一个"信号中心" 某个任务执行完成,就向信号中心"发布"(publish)一个信号 其他任务可以向信号中心"订阅"(subscribe)这个信号 当"信号中心"接受一个"发布"信号时,通知相应“订阅者”开始执行任务又称"观察者模式"。
首先,f2向"信号中心"jQuery订阅"done"信号。
jQuery.subscribe("done", f2);
然后,f1发布信号:
function f1(){ jQuery.publish("done"); } f1执行完成后,向"信号中心"jQuery发布"done"信号, 从而引发f2的执行。
取消订阅。
jQuery.unsubscribe("done", f2);
总结:这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
Promise对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
示例:f1的回调函数f2:
f1().then(f2);
f1要进行如下改写: function f1(){ var dfd = $.Deferred(); setTimeout(function () { // f1的任务代码 dfd.resolve(); }, 0); return dfd.promise; }
优点:
回调函数变成了链式写法,程序的流程清楚,而且有整套的配套方法,可以实现许多强大的功能。
指定多个回调函数:
f1().then(f2).then(f3);
多种类型的回调:
f1().then(f2).fail(f3).always();
如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。不用担心是否错过了某个事件或信号
缺点:不易理解和编写
场景:
我们需要依次:
ajax从服务器获取A loop遍历B ajax从服务器获取C loop遍历D通常的做法是,为它们指定回调函数(callback)
ajax(loop(ajax(loop)))
进化版:
ajax( loop( ajax( loop ) ) )
deferred对象是jQuery的回调函数解决方案。
defer的翻译是"延迟",所以deferred对象的含义就是"延迟"到未来某个点再执行。
它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。
jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。
简易的Deferred
var Deferred = function () { var callbacks = []; return { done: function (fn) { callbacks.push(fn); }, resolve: function (arg) { $.each(callbacks,function(index,callback){ callback(arg) }) } }; };
实例:
var fn1 = function(){ var dtd = $.Deferred(); // 新建一个deferred对象 var tasks = function(){ alert("执行完毕!"); dtd.resolve(); // 改变deferred对象的执行状态 }; setTimeout(tasks,5000); return dtd; }; $.ajax().done(fn); fn1.done();
ajax和普通方法都必须返回一个deferred对象
分析
var fn1 = function(){ var dtd = $.Deferred(); var tasks = function(){ dtd.resolve(); }; setTimeout(tasks,5000); return dtd; }; fn1.done();
steps:
fn1 return dtd dtd.done 注册done事件 执行fn1 fn1完成后dtd.resolve 触发done事件$.ajax().done()指定同一操作的多个回调函数
$.ajax().done().fail()为多个操作指定回调函数
$.when($.ajax(),$.ajax).done()普通操作的回调函数接口
$.when(wait()).done()
jQuery的ajax操作的传统写法:
$.ajax({ url: "test.html", success: function(){ alert("哈哈,成功了!"); }, error:function(){ alert("出错啦!"); } });
现在,新的写法是这样的:
$.ajax("test.html") .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出错啦!"); });
deferred对象的一大好处,就是它允许你自由添加多个回调函数。
$.ajax("test.html") .done(function(){ alert("哈哈,成功了!");} ) .fail(function(){ alert("出错啦!"); } ) .done(function(){ alert("第二个回调函数!");} );
deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。
$.when($.ajax("test1.html"), $.ajax("test2.html")) .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出错啦!"); });
B:ajax是jQuery包装好的返回deferred对象的方法
Q:如何让普通的方法也能享受deferred的便易呢?
deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作
我们来看一个具体的例子。假定有一个很耗时的操作wait:
var wait = function(){ var tasks = function(){ alert("执行完毕!"); }; setTimeout(tasks,5000); };
我们为它指定回调函数,应该怎么做呢?
很自然的,可以使用$.when():
$.when(wait()) .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出错啦!"); });
done()方法立即执行,没有起到回调函数的作用。
why?
原因在deferred思想中提到过 方法必须返回deferred对象,所以必须对wait()进行改写:
var dtd = $.Deferred(); // 新建一个deferred对象 var wait = function(){ var tasks = function(){ alert("执行完毕!"); dtd.resolve(); // 改变deferred对象的执行状态 }; setTimeout(tasks,5000); return dtd.promise(); };
dtd.promise()在原来的deferred对象上返回另一个deferred对象, 只开放与改变执行状态无关的方法(比如done()方法和fail()方法 屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被外部改变。
ajax操作,deferred对象会根据返回结果,自动改变自身的执行状态;
但是,在普通方法wait()函数中,这个执行状态必须由程序员手动指定。
dtd.resolve()的意思是,将dtd对象的执行状态从"未完成"改为"已完成",从而触发done()方法。
deferred.reject()方法,作用是将dtd对象的执行状态从"未完成"改为"已失败",从而触发fail()方法。
JavaScript原生异步编程的Promise模式有着自己的规范Promises/A+
一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
promise必须实现then方法(可以说,then就是promise的核心)
then方法接受两个参数
不管是deferer promise callbacks实质都是包装的回调的方法 只是更加友好 有没有跳出来的方法的
基础
generator 生成器 原理:把yield比作多个事物的原料 当所有yield原料都准备好了后 具体什么时候生产产品(执行)由我们决定 (顺序:依次生产)
function* anotherGenerator(i) { yield i + 1;//index 2 yield i + 2;//index 3 yield i + 3;//index 4 } function* generator(i){ yield i; //index 1 yield* anotherGenerator(i);//index 2-4 yield i + 10;//index 5 } var gen = generator(10);//返回一个这个生成器函数的迭代器(iterator)对象。 console.log("--------------"); console.log(gen.next().value); // 10 console.log(gen.next().value); // 11 console.log(gen.next().value); // 12 console.log(gen.next().value); // 13 console.log(gen.next().value); // 20 console.log("--------------");
迭代器协议 iterator
ES6里的迭代器是一种协议 (protocol). 所有遵循了这个协议的对象都可以称之为迭代器对象.
核心:
next 的方法, 调用该方法后会返回一个拥有两个属性的对象:
done 布尔值, 表示该迭代器是否已经被迭代完毕
function iterator(){ var index = 0; return { next: function(){ return {value: index++, done: false}; } } } var it = iterator(); console.log(it.next().value); // '0' console.log(it.next().value); // '1' console.log(it.next().value); // '2'
现有方法:
使用co函数库
co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象。
ES7 async/await
var asyncReadFile = async function (){ var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
不同:async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await
优点:
内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器
更好的语义。 async 和 await,比起星号和 yield,语义更清楚了
更广的适用性。
前面deferred基本实现了异步编程的流程控制 为什么还需要callbacks呢?
是的 callbacks就是实现队列控制的
var Callbacks = { callbacks: [], add: function(fn) { this.callbacks.push(fn); }, fire: function() { this.callbacks.forEach(function(fn) { fn(); }) } }
添加懒人写法
添加扩展方法
jQuery.Callbacks("once memory")
使用createOptions参数调整并缓存
jQuery.Callbacks({once:true,memory:true})
// String to Object options format cache var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; }
jquery的队列都是按序执行 为什么不用双向链表 方便实现队列的插入、删除等操作 改变执行顺序呢