首页 web前端 js教程 JavaScript队列函数和异步详解_javascript技巧

JavaScript队列函数和异步详解_javascript技巧

Jun 28, 2017 pm 01:21 PM
javascript js 详解

这篇文章主要为大家详细介绍了JavaScript队列函数和异步执行的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

编辑注:在Review别人的JavaScript代码时曾看到过类似的队列函数,不太理解,原来这个是为了保证函数按顺序调用。读了这篇文章之后,发现还可以用在异步执行等。

假设你有几个函数fn1、fn2和fn3需要按顺序调用,最简单的方式当然是:

fn1();
fn2();
fn3();
登录后复制

但有时候这些函数是运行时一个个添加进来的,调用的时候并不知道都有些什么函数;这个时候可以预先定义一个数组,添加函数的时候把函数push 进去,需要的时候从数组中按顺序一个个取出来,依次调用:

var stack = [];
// 执行其他操作,定义fn1
stack.push(fn1);
// 执行其他操作,定义fn2、fn3
stack.push(fn2, fn3);
// 调用的时候
stack.forEach(function(fn) { fn() });
登录后复制

这样函数有没名字也不重要,直接把匿名函数传进去也可以。来测试一下:

var stack = [];
function fn1() {
  console.log('第一个调用');
}
stack.push(fn1);

function fn2() {
  console.log('第二个调用');
}
stack.push(fn2, function() { console.log('第三个调用') });

stack.forEach(function(fn) { fn() }); // 按顺序输出'第一个调用'、'第二个调用'、'第三个调用'
登录后复制

这个实现目前为止工作正常,但我们忽略了一个情况,就是异步函数的调用。异步是JavaScript 中无法避免的一个话题,这里不打算探讨JavaScript 中有关异步的各种术语和概念,请读者自行查阅(例如某篇著名的评注)。如果你知道下面代码会输出1、3、2,那请继续往下看:

console.log(1);

setTimeout(function() {
  console.log(2);
}, 0);

console.log(3);
登录后复制

假如stack 队列中有某个函数是类似的异步函数,我们的实现就乱套了:

var stack = [];

function fn1() { console.log('第一个调用') };
stack.push(fn1);

function fn2() {
  setTimeout(function fn2Timeout() {
     console.log('第二个调用');
  }, 0);
}
stack.push(fn2, function() { console.log('第三个调用') });

stack.forEach(function(fn) { fn() }); // 输出'第一个调用'、'第三个调用'、'第二个调用'
登录后复制

问题很明显,fn2确实按顺序调用了,但setTimeout里的function fn2Timeout() { console.log(‘第二个调用') }却不是立即执行的(即使把timeout 设为0);fn2调用之后马上返回,接着执行fn3,fn3执行完了然才真正轮到fn2Timeout。

怎么解决?我们分析下,这里的关键在于fn2Timeout,我们必须等到它真正执行完才调用fn3,理想情况下大概像这样:

function fn2() {
  setTimeout(function() {
    fn2Timeout();
    fn3();
  }, 0);
}
登录后复制

但这样做相当于把原来的fn2Timeout整个拿掉换成一个新函数,再把原来的fn2Timeout和fn3插进去。这种动态改掉原函数的写法有个专门的名词叫Monkey Patch。按我们程序员的口头禅:“做肯定是能做”,但写起来有点拧巴,而且容易把自己绕进去。有没更好的做法?
我们退一步,不强求等fn2Timeout完全执行完才去执行fn3,而是在fn2Timeout函数体的最后一行去调用:

function fn2() {
  setTimeout(function fn2Timeout() {
    console.log('第二个调用');
    fn3();    // 注{1}
  }, 0);
}
登录后复制

这样看起来好了点,不过定义fn2的时候都还没有fn3,这fn3哪来的?

还有一个问题,fn2里既然要调用fn3,那我们就不能通过stack.forEach去调用fn3了,否则fn3会重复调用两次。

我们不能把fn3写死在fn2里。相反,我们只需要在fn2Timeout末尾里找出stack中fn2的下一个函数,再调用:

function fn2() {
  setTimeout(function fn2Timeout() {
    console.log('第二个调用');
    next();
  }, 0);
}
登录后复制

这个next函数负责找出stack 中的下一个函数并执行。我们现在来实现next:

var index = 0;

function next() {
  var fn = stack[index];
  index = index + 1; // 其实也可以用shift 把fn 拿出来
  if (typeof fn === 'function') fn();
}
登录后复制

next通过stack[index]去获取stack中的函数,每调用next一次index会加1,从而达到取出下一个函数的目的。
next这样使用:

var stack = [];

// 定义index 和next

function fn1() {
  console.log('第一个调用');
  next(); // stack 中每一个函数都必须调用`next`
};
stack.push(fn1);

function fn2() {
  setTimeout(function fn2Timeout() {
     console.log('第二个调用');
     next(); // 调用`next`
  }, 0);
}
stack.push(fn2, function() {
  console.log('第三个调用');
  next(); // 最后一个可以不调用,调用也没用。
});

next(); // 调用next,最终按顺序输出'第一个调用'、'第二个调用'、'第三个调用'。
登录后复制

现在stack.forEach一行已经删掉了,我们自行调用一次next,next会找出stack中的第一个函数fn1执行,fn1 里调用next,去找出下一个函数fn2并执行,fn2里再调用next,依此类推。
每一个函数里都必须调用next,如果某个函数里不写,执行完该函数后程序就会直接结束,没有任何机制继续。

了解了函数队列的这个实现后,你应该可以解决下面这道面试题了:

// 实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)
/* 输出: 
Hi! This is Hank!
*/

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
/* 输出: 
Hi! This is Hank!
// 等待10秒..
Wake up after 10
Eat dinner~
*/

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)
/* 输出: 
Hi This is Hank!
Eat dinner~
Eat supper~
*/

LazyMan(“Hank”).sleepFirst(5).eat(“supper”)
/* 等待5秒,输出
Wake up after 5
Hi This is Hank!
Eat supper
*/

// 以此类推。
登录后复制

Node.js 中大名鼎鼎的connect框架正是这样实现中间件队列的。有兴趣可以去看看它的源码或者这篇解读《何为 connect 中间件》。

细心的你可能看出来,这个next暂时只能放在函数的末尾,如果放在中间,原来的问题还会出现:

function fn() {
  console.log(1);
  next();
  console.log(2); // next()如果调用了异步函数,console.log(2)就会先执行
}
登录后复制

redux 和koa 通过不同的实现,可以让next放在函数中间,执行完后面的函数再折回来执行next下面的代码,非常巧妙。有空再写写。

以上是JavaScript队列函数和异步详解_javascript技巧的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Win11管理员权限获取详解 Win11管理员权限获取详解 Mar 08, 2024 pm 03:06 PM

Windows操作系统是全球最流行的操作系统之一,其新版本Win11备受瞩目。在Win11系统中,管理员权限的获取是一个重要的操作,管理员权限可以让用户对系统进行更多的操作和设置。本文将详细介绍在Win11系统中如何获取管理员权限,以及如何有效地管理权限。在Win11系统中,管理员权限分为本地管理员和域管理员两种。本地管理员是指具有对本地计算机的完全管理权限

Oracle SQL中的除法运算详解 Oracle SQL中的除法运算详解 Mar 10, 2024 am 09:51 AM

OracleSQL中的除法运算详解在OracleSQL中,除法运算是一种常见且重要的数学运算操作,用于计算两个数相除的结果。除法在数据库查询中经常用到,因此了解OracleSQL中的除法运算及其用法是数据库开发人员必备的技能之一。本文将详细讨论OracleSQL中除法运算的相关知识,并提供具体的代码示例供读者参考。一、OracleSQL中的除法运算

推荐:优秀JS开源人脸检测识别项目 推荐:优秀JS开源人脸检测识别项目 Apr 03, 2024 am 11:55 AM

人脸检测识别技术已经是一个比较成熟且应用广泛的技术。而目前最为广泛的互联网应用语言非JS莫属,在Web前端实现人脸检测识别相比后端的人脸识别有优势也有弱势。优势包括减少网络交互、实时识别,大大缩短了用户等待时间,提高了用户体验;弱势是:受到模型大小限制,其中准确率也有限。如何在web端使用js实现人脸检测呢?为了实现Web端人脸识别,需要熟悉相关的编程语言和技术,如JavaScript、HTML、CSS、WebRTC等。同时还需要掌握相关的计算机视觉和人工智能技术。值得注意的是,由于Web端的计

PHP模运算符的作用及用法详解 PHP模运算符的作用及用法详解 Mar 19, 2024 pm 04:33 PM

PHP中的模运算符(%)是用来获取两个数值相除的余数的。在本文中,我们将详细讨论模运算符的作用及用法,并提供具体的代码示例来帮助读者更好地理解。1.模运算符的作用在数学中,当我们将一个整数除以另一个整数时,会得到一个商和一个余数。例如,当我们将10除以3时,商为3,余数为1。模运算符就是用来获取这个余数的。2.模运算符的用法在PHP中,使用%符号来表示模

详细解析C语言学习路线 详细解析C语言学习路线 Feb 18, 2024 am 10:38 AM

C语言作为一门广泛应用在软件开发领域的编程语言,是很多程序员初学者的首选。学习C语言不仅可以帮助我们建立编程的基础知识,还可以提升我们解决问题和思考的能力。本文将详细介绍一条C语言学习的路线图,帮助初学者更好地规划自己的学习进程。1.学习基本语法在开始学习C语言之前,我们首先需要了解C语言的基本语法规则。这包括变量和数据类型、运算符、控制语句(如if语句、

linux系统调用system()函数详解 linux系统调用system()函数详解 Feb 22, 2024 pm 08:21 PM

Linux系统调用system()函数详解系统调用是Linux操作系统中非常重要的一部分,它提供了一种与系统内核进行交互的方式。其中,system()函数是一个常用的系统调用函数之一。本文将详细介绍system()函数的使用方法,并提供相应的代码示例。系统调用的基本概念系统调用是用户程序与操作系统内核交互的一种方式。用户程序通过调用系统调用函数来请求操作系统

Linux的curl命令详解 Linux的curl命令详解 Feb 21, 2024 pm 10:33 PM

Linux的curl命令详解摘要:curl是一种强大的命令行工具,用于与服务器进行数据通信。本文将介绍curl命令的基本用法,并提供实际的代码示例,帮助读者更好地理解和应用该命令。一、curl是什么?curl是一个命令行工具,用于发送和接收各种网络请求。它支持多种协议,如HTTP、FTP、TELNET等,并提供了丰富的功能,如文件上传、文件下载、数据传输、代

深入了解Promise.resolve() 深入了解Promise.resolve() Feb 18, 2024 pm 07:13 PM

Promise.resolve()详解,需要具体代码示例Promise是JavaScript中一种用于处理异步操作的机制。在实际开发中,经常需要处理一些需要按顺序执行的异步任务,而Promise.resolve()方法就是用来返回一个已经Fulfilled状态的Promise对象。Promise.resolve()是Promise类的一个静态方法,它接受一个

See all articles