coder都知道,javascript语言运行环境是单线程的,这意味着任何两行代码都不能同时运行。多任务同时进行时,实质上形成了一个队列,当队列中前一个事件结束时,才执行下一个事件。 如果队列中任何一个事务费时太长,则会造成浏览器假死,阻塞其他事务正常进行,影响用户体验。
js中将任务执行分为同步模式和异步模式,上面一种即为同步模式,任何比较花时间的代码最好设计成异步模式。通过异步编程方式,即可以达到伪多进程。JavaScript中常见的异步模式主要有callbaks,listeners(一种观察者模式),promise,下面我将会详细描述三者各自的优劣。
三种异步编程模式:
1、Callbacks
2、Listeners
3、Promise
第一种:Callbacks
这是再常见不过的异步编程方式了,任何前端都写过这样的代码吧:
function callback(){
console.log('do callbacks');
};
setTimeout(callback,300);
通过这种方式,你可以简单的实现异步编程。这里需要注意,这里的300ms只是理想时间,实际中各浏览器略有差异,主要取决于线程中其他任务所用的时间。这个不具体讨论,其中还涉及requestAnimationFrame等。自己可以google。
此方法缺点是什么想必我不说你也知道,如下代码:
function fn1(f){
alert(1);
f();
};
function fn2(f){
alert(2);
f();
};
function fn3(){
alert(3);
};
fn1(function(){ fn2(fn3); });
如果逻辑在复杂一点呢?恐怕看代码的人会一头雾水吧。而promise则可以把这种复杂的嵌套写的一看就明白,下面会讲到。
第二种:Listeners
这也是一种常见的异步编程方式,如下代码:
$elem.on('click',doClick);
function doClick(){
//do something
};
设计模式中将这种方式称之为命令模式。这种方式的好处是你可以注册多个事件,待事件触发时调用注册的函数。
当然这里不仅仅是这种简单的监听方式,我需要讲的是一种更抽象的异步编程方式——listeners。
通过观察者模式,我们用一个数组来保存事件名称以及事件绑定的函数方法,当需要触发该事件时,我们从数组中取出之前的注册的函数。而EventProxy就是这种方式实现的,通过on,all注册,emit触发。如:
var ep = EventProxy.create("template", "data", "l10n", function (template, data, l10n) {
_.template(template, data, l10n);
});
$.get("template", function (template) {
// something
ep.emit("template", template);
});
$.get("data", function (data) {
// something
ep.emit("data", data);
});
$.get("l10n", function (l10n) {
// something
ep.emit("l10n", l10n);
});
第三种:Promise
最后一种方法隆重登场,也是现在讨论最多的异步编程方式。
代码其实是现实的归纳总结并以另外一种方式展现,我们有时候看不懂,是因为写代码方式跟现实有差异。假如代码逻辑这么写:
img1.callThisIfLoadedOrWhenLoaded(function() {
// 加载完成
}).orIfFailedCallThis(function() {
// 加载失败
});
// 以及……
whenAllTheseHaveLoaded([img1, img2]).callThis(function(){
// 全部加载完成
}).orIfSomeFailedCallThis(function(){
// 一个或多个加载失败
});
我相信非程序员也能看懂吧。这就是我们要介绍的Promise异步编程方法。现有一些插件库Q.js, when.js ,wind.js ,rsvp.js都是类似实现的,遵守一个通用的、标准化的规范:Promises/A+,jQuery 有个类似的方法叫 Deferred,但不兼容 Promises/A+ 规范,于是会有点小问题,使用需谨慎。jQuery 还有一个 Promise 类型,它其实是 Deferred 的缩减版,所以也有同样问题。jQuery相关的后续我会分章节逐一讲述,这里不做展开。
Promise异步编程总结起来就是实现了thenable,说的更直白的一点就是我跟你是好朋友,我可能有事需要你帮忙,你随时候着吧,有需要我会第一时间通知你。你跟小王也是好哥们,并同样告诉你需要他帮忙,并第一时间通知他。当然你通知他是在我通知你以后。用伪代码表示:
myself(function(){
callXiaoLi();
}).then(function(){
//我是小李,你喊我了,我就去喊小王
callXiaowang();
}).then(function(){
console.log('我是小王');
});
then实现比较绕,需要解释一下,如:
.then(xx).then()
每次.then时会生成一个新的promise对象, 就是说后一个then是注册给上一个promise对象的,其出发条件依赖于上一个.then时生成的promise。
如果上一个.then 最后返回结果是非promise对象(即没有then方法),则直接 resolve
如果上一个.then最后返回的结果是promise对象,那.then生成的promise必须依赖返回结果中的promise,及内层的 promise.then( “.then时生成的promise”.resolve)
可能我这么解释还是没能解释很清楚,自己去理解吧。
另外,谷歌浏览器已经实现了Promise API。我使用的式chrome 版本 31.0.1650.63。不行你可以在控制台输入以下代码测试:
var p = new Promise(function(resolve,reject){
setTimeout(resolve,100);
setTimeout(reject,300);
});
p.then(function(){
alert('resolve');
},function(){
alert('reject');
});
好了,异步编程的方式就讲这么多,我这里只不过是抛砖引玉,并没有详细阐述其api,而是以自己的代码简单实现其原来。这些异步编程方式不仅仅限于浏览器环境,服务器环境如nodejs也可使用。比如EventProxy本来就是在nodejs中发展而来的。
希望通过我的简单讲述能给大家提供帮忙。如果有错,请帮忙指出,谢谢。