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的队列都是按序执行 为什么不用双向链表 方便实现队列的插入、删除等操作 改变执行顺序呢