当前位置:首页 > 日记 > 正文

JavaScript队列函数和异步执行详解

JavaScript队列函数和异步执行详解

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

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

fn1();fn2();fn3();

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

var stack = [];// 执行其他操作,定义fn1stack.push(fn1);// 执行其他操作,定义fn2、fn3stack.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 和nextfunction 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 10Eat 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 5Hi 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下面的代码,非常巧妙。有空再写写。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

相关文章

百度编辑器 ueditor 内容编辑自动

百度编辑器 ueditor 内容编辑自动

标签,替换,百度编辑器,编辑,内容,如图,红框为回车键和shift+回车 : ===>> ueditor.all.js中:1:搜索修改成false:allowDivTransToP: false再搜索并修改以下://编辑器不能为空内容if (domUtils.isEmptyNode(me.body)) {me.body.innerHTML = '<…

PS怎么使用滤镜制作黑白城市艺术?

PS怎么使用滤镜制作黑白城市艺术?

滤镜,黑白,艺术,城市,电脑软件,简单创意的PS教程,效果图虽然只用了简单的动感模糊滤镜,不过效果就非常有创意,建筑有一种动感效果,画面也简洁了很多。喜欢的同学可以去尝试一下。最终效果图如下:软件名称:Adobe Photoshop 8.0 中文完整绿色破解版…

ps怎么给帅哥穿上个性的东北大花衣

ps怎么给帅哥穿上个性的东北大花衣

东北,穿上,大花,帅哥,衣服,衣服增加布纹的方法有很多,例如:直接把衣服单独抠出,并用想要的布纹剪贴到原衣服上;后期把原衣服的褶皱还原到布纹上,再调整颜色和光影等,得到想要的效果。软件名称:Adobe Photoshop 8.0 中文完整绿色破解版软件大小:150.…

PS怎么设计一款银色金属字体立体效

PS怎么设计一款银色金属字体立体效

字体,银色,效果,电脑软件,PS,我们会经常碰到金属字的制作工作,在这样的一个章节里面,我们就来学习一下银色金属字的工作,看看如何通过各种工具来完成这样的一个艺术字的工作。软件名称:Adobe photoshop 6.0 汉化中文免费版软件大小:107MB更新时…

ps把人物做成素描效果教程

ps把人物做成素描效果教程

教程,素描,效果,人物,电脑软件,ps怎样把人物照片做成素描效果?运用PHOTOSHOP软件,可以很简单的把人物照片制作成素描画的效果,下面和小编一起来看看具体步骤吧。ps把人物图片做成素描效果步骤: 1、打开图片,复制一个图层,然后去色(快捷键是ctrl+s…

Photoshop快速的把漫画人物照片变

Photoshop快速的把漫画人物照片变

教程,照片,效果,快速,漫画,效果图:主要过程:1、将我们的背景黑板素材和漫画素材拖拽进PS同时右键单击漫画图层,将其转化为智能对象以方便我们的后期操作2、点击滤镜-风格化-查找边缘先将素材的轮廓勾画出来。3、接下来我们对素材进行反向并应…

ppt2010怎么拆分汉字

ppt2010怎么拆分汉字

拆分,方法,汉字,电脑软件,strong,  在PPT制作过程中,有时我们需要将汉字一笔笔进行拆分,然后按笔画顺序制作成动画。尤其是在汉字教学过程中。那么要怎样做呢?接下来小编告诉你ppt2010拆分汉字的方法。ppt2010拆分汉字的方法首先我们需要打…

Photoshop模拟绘制逼真的挂式空调

Photoshop模拟绘制逼真的挂式空调

图标,绘制,模拟,教程,逼真,时间过的真快,不知不觉已经是夏天了。夏天怎么少的了空调呢?今天小编给大家带来Photoshop模拟绘制逼真的挂式空调轻拟物图标教程,感兴趣的朋友可以一起动手试试哦效果图:主要过程:教程结束,以上就是Photoshop模拟绘制逼…

ASP.NET中GridView、DataList、Dat

ASP.NET中GridView、DataList、Dat

遍历,控件,数据,示例,电脑软件,本文实例讲述了ASP.NET中GridView、DataList、DataGrid三个数据控件foreach遍历用法。分享给大家供大家参考,具体如下://gridview遍历如下: foreach (GridViewRow row in GridView1.Rows){ CheckBox cb = (Chec…

JavaScript html5 canvas实现上画

JavaScript html5 canvas实现上画

超链接,上画,电脑软件,JavaScript,canvas,本文实例为大家分享了html5 canvas在图片上画超链接的具体代码,供大家参考,具体内容如下1. html<canvas id="canvasFile" style="margin-top:15px;" width="500" height="400"></canvas> <in…

word中多张排版的方法教程详解

word中多张排版的方法教程详解

步骤,方法,教程,多张,详解,  在工作当中,我们经常会使用到办公软件office系列,尤其是Word。在对文档进行编辑的过程中,肯定避免不了插入多张图片、表格、形状、文本框等。但是由于图片、表格、形状、文本框如果正常的编辑的话,经常会按回…

手机wps怎样传送文件

手机wps怎样传送文件

文件,方法,传送,文件传送,电脑软件,  现在手机已经越来越智能了,很多人都会在手机上安装wps办公软件,有时候当我们没有电脑传wps文件时,直接就可以用手机传文件,那么手机wps怎样传送文件呢?下面是小编整理的手机wps传文件的方法,希望能帮到大家…