文章目录
一.异步操作概述
JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。
注意,JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,单个脚本只能在一个线程上运行(称为主线程),其他线程都是在后台配合
同步任务和异步任务
程序里面所有的任务,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。
同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有”堵塞“效应。
任务队列和事件循环
JavaScript 运行时,除了一个正在运行的主线程,引擎还提供一个任务队列(task queue),里面是各种需要当前程序处理的异步任务。
首先,主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等到执行完,下一个异步任务再进入主线程开始执行。一旦任务队列清空,程序就结束执行。
事件循环:JavaScript 引擎不停地检查挂起来的异步任务,是不是可以进入主线程了
一.异步操作的模式
1.回调函数
function f1(callback) {
// ...
callback();
}
function f2() {
// ...
}
f1(f2);
f2必须等到f1执行完成,才能执行
2.事件监听
另一种思路是采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
f1.on('done', f2); //jquery语法
function f1() {
setTimeout(function () {
// ...
f1.trigger('done');
}, 1000);
}
f1发生done事件,就执行f2
3.发布/订阅
事件完全可以理解成”信号“,如果存在一个”信号中心“,某个任务执行完成,就向信号中心”发布“(publish)一个信号,其他任务可以向信号中心”订阅“(subscribe)这个信号,从而知道什么时候自己可以开始执行。
jQuery.subscribe('done', f2);
function f1() {
setTimeout(function () {
// ...
jQuery.publish('done');
}, 1000);
}
f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。
jQuery.unsubscribe('done', f2); //也可以取消订阅
4.定时器
(1)setTimeout()
setTimeout
函数用来指定某个函数或某段代码,在多少毫秒之后执行
var timer = setTimeout(function (a,b) {
console.log(a + b);
}, 1000, 1, 1);
//setTimeout共有4个参数。最后那两个参数,将在1000毫秒之后回调函数执行时,作为回调函数的参数
clearTimeout(timer); //取消定时器
(2)setInterval()
setInterval
函数的用法与setTimeout
完全一致,区别仅仅在于setInterval
指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行
var i = 1
var timer = setInterval(function() {
console.log(2);
}, 1000)
每隔1000毫秒就输出一个2,会无限运行下去
clearInterval(timer); //取消定时器
(3)setTimeout(f, 0)
setTimeout
的作用是将代码推迟到指定时间执行,如果指定时间为0
,就无法等到当前脚本的同步任务,所以
定时器不会在当前的同步任务中执行。
setTimeout(f, 0)
这种写法的目的是,尽可能早地执行f
,但是并不能保证立刻就执行f
setTimeout(function () {
console.log(1);
}, 0);
console.log(2);
// 2
// 1
2是同步任务,在本轮事件循环执行,而1是下一轮事件循环执行。
整事件的发生顺序
setTimeout(f, 0)
的作用:可以调整事件的发生顺序,让父元素的事件回调函数先发生
由于事件冒泡的原因,子元素的事件回调函数,会早于父元素
var input = document.getElementById('myButton');
input.onclick = function A() {
setTimeout(function B() {
input.value +=' input';
}, 0)
};
document.body.onclick = function C() {
input.value += ' body'
};
点击按钮后,先触发回调函数A,然后触发函数C。函数A中,setTimeout将函数B推迟到下一轮事件循环执行
5.Promise 对象
(1)Promise 的含义
Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
两个特点
(1)对象的三种状态[pending
(进行中)、resolved
(已成功)和rejected
(已失败)]不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
(2)Promise 基本用法
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数
三.异步操作的流程控制
如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守这种顺序
1.串行执行
我们可以编写一个流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。这就叫串行执行
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
function async(arg, callback) {
console.log('参数为 ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function final(value) {
console.log('完成: ', value);
}
function series(item) {
if(item) {
async( item, function(result) {
results.push(result);
return series(items.shift());
});
} else {
return final(results[results.length - 1]);
}
}
series(items.shift()); //用时6秒
函数series就是串行函数,它会依次执行异步任务,所有任务都完成后,才会执行final函数。items数组保存每一个异步任务的参数,results数组保存每一个异步任务的运行结果。
2.并行执行
流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final
函数。
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
function async(arg, callback) {
console.log('参数为 ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function final(value) {
console.log('完成: ', value);
}
items.forEach(function(item) { //用时1秒
async(item, function(result){
results.push(result);
if(results.length === items.length) {
final(results[results.length - 1]);
}
})
});
forEach方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final函数。
3.并行与串行的结合
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var limit = 2;
function async(arg, callback) {
console.log('参数为 ' + arg +' , 1秒后返回结果');
setTimeout(function () { callback(arg * 2); }, 1000);
}
function final(value) {
console.log('完成: ', value);
}
function launcher() {
while(running < limit && items.length > 0) {
var item = items.shift();
async(item, function(result) {
results.push(result);
running--;
if(items.length > 0) {
launcher();
} else if(running == 0) {
final(results);
}
});
running++;
}
}
launcher(); //需要3秒,通过调节limit变量,达到效率和资源的最佳平衡
同时运行两个异步任务。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数。