摘要:回调函数指定了下一步操作。异步操作的流程控制参数为秒后返回结果上面代码的函数是一个异步任务,非常耗时,每次执行需要秒才能完成,然后再调用回调函数。
单线程模型
同步任务和异步任务
任务队列和事件循环
异步操作的模式
回调函数
事件监听
发布/订阅
异步操作的流程控制
串行执行
并行执行
并行与串行的结合
1.单线程模型
指的是js只在线程运行,一个时间执行一个任务,其他任务排队。事实上是一个运行脚本的主线程加多个后台配合的线程。
事件循环机制
Js单线程模式使得cpu空闲,io操作慢(Ajax请求网络资源)。cpu不管io操作,挂起任务,运行排队后面的任务,等io完成再执行的机制。
2.同步任务和异步任务
任务全部分为这两类。
同步任务在主线程的任务按排队顺序执行。
异步任务指的是被引擎挂到一边,不在主线程而进去任务队列的任务。等到可以执行了,该任务采用回调函数的方式进入主线程。在他后面的任务不等他结束马上执行。
举例来说,Ajax 如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。
3.任务队列和事件循环
Js除了正在运行的主线程,还有一个异步任务队列,
主线程先完成全部同步任务,在检查异步任务是否可以执行,可以的话安排进主线程,此时就变成同步任务了,然后继续检查。直到任务队列为空。
异步任务主要写法是回调函数,因为有回调函数才会进入任务队列,等重新进入主线程,马上执行回调函数。回调函数指定了下一步操作。
JavaScript 引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案就是引擎在不停地检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环(Event Loop)
4.异步操作的模式
4.1回调函数
下面是两个函数f1和f2,编程的意图是f2必须等到f1执行完成,才能执行。
function f1() {
// ...
}
function f2() {
// ...
}
f1();
f2();
上面代码的问题在于,如果f1是异步操作,f2会立即执行,不会等到f1结束再执行。
这时,可以考虑改写f1,把f2写成f1的回调函数。
function f1(callback) {
// ...
callback();
}
function f2() {
// ...
}
f1(f2);
易理解实现
不利于阅读和维护,高耦合。
4.2事件监听
另一种思路是采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
f1.on("done", f2);
上面这行代码的意思是,当f1发生done事件,就执行f2。然后,对f1进行改写:
function f1() {
setTimeout(function () {
// ... f1.trigger("done");
}, 1000);
}
可绑定多个事件,每个事件触发多个回调,易理解,去耦合。
4.3发布/订阅
某个任务(异步)完成,发布信号。多个任务订阅(回调),从而知道自己什么时候执行。
首先,f2向信号中心jQuery订阅done信号。
jQuery.subscribe("done", f2);
然后,f1进行如下改写。
function f1() {
setTimeout(function () {
// ... jQuery.publish("done");
}, 1000);
}
上面代码中,jQuery.publish("done")的意思是,f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。
f2完成执行后,可以取消订阅(unsubscribe)。
jQuery.unsubscribe("done", f2);
这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
5.异步操作的流程控制
function async(arg, callback) {
console.log("参数为 " + arg +" , 1秒后返回结果");
setTimeout(function () { callback(arg * 2); }, 1000);
}
上面代码的async函数是一个异步任务,非常耗时,每次执行需要1秒才能完成,然后再调用回调函数。
如果有六个这样的异步任务,需要全部完成后,才能执行最后的final函数。请问应该如何安排操作流程?
function final(value) {
console.log("完成: ", value);
}
async(1, function (value) {
async(2, function (value) {
async(3, function (value) { async(4, function (value) { async(5, function (value) { async(6, final); }); }); });
});
});
// 参数为 1 , 1秒后返回结果
// 参数为 2 , 1秒后返回结果
// 参数为 3 , 1秒后返回结果
// 参数为 4 , 1秒后返回结果
// 参数为 5 , 1秒后返回结果
// 参数为 6 , 1秒后返回结果
// 完成: 12
上面代码中,六个回调函数的嵌套,不仅写起来麻烦,容易出错,而且难以维护。
5.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());
上面代码中,函数series就是串行函数,它会依次执行异步任务,所有任务都完成后,才会执行final函数。items数组保存每一个异步任务的参数,results数组保存每一个异步任务的运行结果。
注意,上面的写法需要六秒,才能完成整个脚本。
5.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) {
async(item, function(result){
results.push(result); if(results.length === items.length) { final(results[results.length - 1]); }
})
});
上面代码中,forEach方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final函数。
相比而言,上面的写法只要一秒,就能完成整个脚本。这就是说,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。因此有了第三种流程控制方式。
5.3并行与串行的结合
所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用系统资源。
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();
上面代码中,最多只能同时运行两个异步任务。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数。
这段代码需要三秒完成整个脚本,处在串行执行和并行执行之间。通过调节limit变量,达到效率和资源的最佳平衡
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/105811.html
摘要:标准库中的所有方法都提供非阻塞的异步版本,并接受回调函数,某些方法还具有对应的阻塞方法,其名称以结尾。比较代码阻塞方法同步执行,非阻塞方法异步执行。 阻塞与非阻塞概述 此概述介绍了Node.js中阻塞与非阻塞调用之间的区别,此概述将引用事件循环和libuv,但不需要事先了解这些主题,假设读者对JavaScript语言和Node.js回调模式有基本的了解。 I/O主要指与libuv支持的...
摘要:即同步请求,浏览器需要等待服务器处理请求,导致了浏览器端的阻塞。这使得应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。引擎在客户端运行,承担了一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载。 前言 博主博客:Stillwater的博客知乎专栏:前端汪汪本文为作者原创转载请注明出处: http://hiztx.top/2017/01/11/a......
摘要:需要注意的是,并不是的替代品,两者各自有其适应的场景。但为了方便交流,我们通常将获取资源的一方称为客户端主要的工具是浏览器,而将派发资源的一方称为服务端又称为服务器。它可以帮助我们为之后概念细节的学习打下良好基础。 再也不学AJAX了是一个与AJAX主题相关的文章系列,包含以下三个部分的内容: AJAX概述:主要回答AJAX是什么这个问题; 使用AJAX:介绍如何通过JavaSc...
摘要:本文主要讲述高可用方案,以及京东云数据库的高可用实现。事务日志传送事务日志传送提供了数据库级别的高可用性保护。拥有镜像角色的伙伴称为镜像服务器,其数据库副本为当前的镜像数据库。 showImg(https://segmentfault.com/img/bVbtNqp?w=688&h=113); 数据库的高可用是指在硬件、软件故障发生时,可以将业务从发生故障的数据库节点迁移至备用节点。本...
阅读 796·2023-04-25 22:57
阅读 3049·2021-11-23 10:03
阅读 612·2021-11-22 15:24
阅读 3155·2021-11-02 14:47
阅读 2900·2021-09-10 11:23
阅读 3114·2021-09-06 15:00
阅读 3935·2019-08-30 15:56
阅读 3321·2019-08-30 15:52