javascript是单线程语言,所谓”单线程”,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种方式比较好控制,不会存在像java那个多线程死锁等等复杂的问题,缺点也很明显,当一个任务执行较长时间时,后续的任务需要等待,会拖延整个程序的执行,使浏览器卡死。
为了解决这个问题,javascript将任务分为两种:同步(Synchronous)和异步(Asynchronous);
在浏览器端,如果需要执行比较耗时的任务,为了避免浏览器卡死,就需要异步执行,不影响其他任务,最常见的例子是ajax操作。
但是一旦使用异步,还要保证有些任务再异步任务之后执行,这个时候就需要特殊的处理了,以下介绍四中常见的处理异步的方法
一、回调函数
这个是最常见的处理方式,假设一个ajax请求,设定一个success方法再请求成功时回调
1 2 3 4 5
| $.ajax( url :'test', success : function(data){ } );
|
这样ajax请求不会阻塞其他程勋的正常运行
回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。
二、事件监听
事件驱动模式。success任务在请求成功事件发生时执行。还是以ajax为例:
1 2 3 4 5 6 7 8 9 10
| function success(){ console.log('the ajax successed'); } $.on('success', success); $.ajax( url : 'test', success : function(){ $.trigger('success'); } );
|
这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以去耦合(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。
三、发布/订阅
基于观察者模式,可以用新闻的方式来理解,例:
1 2 3 4 5 6 7 8 9
| $.subscribe('news', function(){ console.log("我来看新闻"); }); $.ajax( url : 'getNews', success : function(){ $.publish("news"); } );
|
当ajax请求完成之后会发布信号告诉所有的订阅者“我已经执行完了”,订阅者接收到后,会执行相应的操作
这种方法的性质与”事件监听”类似,但是明显优于后者。因为我们可以通过查看”消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
四、Promise对象
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
简单说,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。就像下面这样:
1
| start().then(step1).then(step2)
|
这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
|
;(function(Global, factory) { if (typeof module === "object" && typeof module.exports === "object") { module.export = factory(); } else if (define && typeof define === 'function') { define(function() { return factory(); }); } else { if (!window.Promise) window.Promise = factory(); } })(window, function() { var Promise = function(startFunc) { this.state = 'pending'; this.thenables = []; if(startFunc && typeof startFunc === 'function'){ startFunc(this.resolve, this.reject); } };
Promise.prototype.resolve = function(value) { if (this.state != 'pending') return; this.state = 'fulfilled'; this.value = value; this._handleThen(); return this; };
Promise.prototype.reject = function(reason) { if (this.state != 'pending') return; this.state = 'rejected'; this.reason = reason; this._handleThen(); return this; }; Promise.prototype.then = function(onFulfilled, onRejected) { var thenable = {}; if (typeof onFulfilled == 'function') { thenable.fulfill = onFulfilled; } if (typeof onRejected == 'function') { thenable.reject = onRejected; }
if (this.state != 'pending') { if (setImmediate) { setImmediate(function() { this._handleThen(); }.bind(this)); } else { setTimeout(function() { this._handleThen(); }.bind(this), 0); } } thenable.promise = new Promise(); this.thenables.push(thenable); return thenable.promise; }; Promise.prototype._handleThen = function() { if (this.state === 'pending') return; if (this.thenables.length) { for (var i = 0; i < this.thenables.length; i++) { var thenPromise = this.thenables[i].promise; var returnedVal; try { switch (this.state) { case 'fulfilled': if (this.thenables[i].fulfill) { returnedVal = this.thenables[i].fulfill(this.value); } else { thenPromise.resolve(this.value); } break; case 'rejected': if (this.thenables[i].reject) { returnedVal = this.thenables[i].reject(this.reason); } else { thenPromise.reject(this.reason); } break; } if (returnedVal === null) { this.thenables[i].promise.resolve(returnedVal); } else if (returnedVal instanceof Promise || typeof returnedVal.then === 'function') { returnedVal.then(thenPromise.resolve.bind(thenPromise), thenPromise.reject.bind(thenPromise)); } else { this.thenables[i].promise.resolve(returnedVal); } } catch (e) { thenPromise.reject(e); } } this.thenables = []; } }; return Promise; });
|
需要注意的地方
- 异步回调函数都是在主任务执行完毕后执行,如果主任务卡死,那么回调函数将不会执行;
- ajax的同步方法最好不要轻易使用,程序需要等待请求成功才会继续向下执行,如果请求时间过长浏览器会卡死;
- 如果你想保证你的某些函数再主函数执行完后再执行可以使用setTimout(function(){},0);