如何在 Javascript 中创建异步函数?
Posted
技术标签:
【中文标题】如何在 Javascript 中创建异步函数?【英文标题】:How can I create an Asynchronous function in Javascript? 【发布时间】:2012-03-19 23:43:18 【问题描述】:看看这个code:
<a href="#" id="link">Link</a>
<span>Moving</span>
$('#link').click(function ()
console.log("Enter");
$('#link').animate( width: 200 , 2000, function()
console.log("finished");
);
console.log("Exit");
);
正如您在控制台中看到的,“动画”函数是异步的,它“分叉”了事件处理程序块代码的流程。事实上:
$('#link').click(function ()
console.log("Enter");
asyncFunct();
console.log("Exit");
);
function asyncFunct()
console.log("finished");
跟随块代码的流程!
如果我希望使用这种行为创建我的function asyncFunct()
,我该如何使用 javascript/jquery?我认为有一种策略不使用setTimeout()
【问题讨论】:
看看 jQuery 源代码 :) .animate() 方法使用回调。当动画完成时,Animate 将调用回调。如果您需要与 .animate() 相同的行为,您需要的是一个回调(在一些其他操作之后由“main”函数调用)。如果您需要一个“完整的”异步函数(一个在不阻塞执行流程的情况下调用的函数),情况就不同了。在这种情况下,您可以使用延迟接近 0 的 setTimeout()。 @Fabio Buda:为什么 callback() 应该实现一种异步?其实不是jsfiddle.net/5H9XT/9 事实上,在“回调”之后,我引用了一个带有 setTimeout 的“完整”异步方法。我的意思是在其他代码之后调用函数的方式将回调作为伪异步:-) 【参考方案1】:您无法创建真正自定义的异步函数。您最终将不得不利用本机提供的技术,例如:
setInterval
setTimeout
requestAnimationFrame
XMLHttpRequest
WebSocket
Worker
一些 html5 API,例如文件 API、Web 数据库 API
支持onload
的技术
...许多其他
其实对于动画jQuery的usessetInterval
。
【讨论】:
我昨天和朋友讨论过这个问题,所以这个答案很完美!我理解并且可以识别异步函数并在 JS 中正确使用它们。但是我并不清楚为什么我们不能实现自定义的。它就像一个黑匣子,我们知道如何使它工作(例如,使用setInterval
),但我们甚至无法打开它来查看它是如何完成的。你碰巧有更多关于这个主题的信息吗?
@MatheusFelipe 这些函数是 JavaScript 引擎实现的原生函数,您唯一可以依赖的就是规范,e.g. HTML5 timers 并相信它们根据规范运行的黑盒特性。
@MatheusFelipe youtu.be/8aGhZQkoFbQ 迄今为止关于这个话题的最佳演讲......
一些实现,特别是 Node.js,支持setImmediate
promises
怎么样。它会给awaitable
吗?【参考方案2】:
您可以使用计时器:
setTimeout( yourFn, 0 );
(其中yourFn
是对您的函数的引用)
或者,Lodash:
_.defer( yourFn );
延迟调用
func
,直到当前调用堆栈被清除。func
在被调用时会提供任何其他参数。
【讨论】:
这不起作用,我在画布中绘制的 javascript 函数不断使 UI 没有响应。 @gab06 - 我会说您的画布绘图功能由于其自身的充分理由而被阻塞。将其操作拆分为许多较小的操作,并使用计时器调用每个操作:您会看到这种方式的界面确实会响应您的鼠标点击等。 根据 HTML5 规范,setTimeout
的最短时间为 4 毫秒。给它 0 仍然需要最少的时间。但是,是的,它作为函数延迟器效果很好。
对于scope.setTimeout
函数,如果省略delay
参数,则默认使用0
的值。【参考方案3】:
这里有简单的解决方案(其他写一下) http://www.benlesh.com/2012/05/calling-javascript-function.html
这里你有以上现成的解决方案:
function async(your_function, callback)
setTimeout(function()
your_function();
if (callback) callback();
, 0);
TEST 1(可能输出'1 x 2 3'或'1 2 x 3'或'1 2 3 x'):
console.log(1);
async(function() console.log('x'), null);
console.log(2);
console.log(3);
TEST 2(将始终输出 'x 1'):
async(function() console.log('x');, function() console.log(1););
这个函数在超时时间为 0 的情况下执行 - 它将模拟异步任务
【讨论】:
TEST 1 实际上只能输出 '1 2 3 x' 并且 TEST 2 保证每次都输出 '1 x'。测试 2 中出现意外结果的原因是因为调用了console.log(1)
,并且输出 (undefined
) 作为第二个参数传递给 async()
。在 TEST 1 的情况下,我认为您并不完全了解 JavaScript 的执行队列。因为对console.log()
的每个调用都发生在同一个堆栈中,所以x
保证最后记录。我会否决这个错误信息的答案,但没有足够的代表。
@Joshua:似乎@fider 打算将 TEST 2 写为:async(function() console.log('x'), function()console.log(1));
。
是的@nzn 和@Joshua 我的意思是TEST 2 as: async(function() console.log('x'), function()console.log(1));
- 我已经更正了
TEST 2 输出为 1 x in async(function() setTimeout(()=>console.log('x');,1000), function() console.log (1););【参考方案4】:
这是一个函数,它接受另一个函数并输出一个异步运行的版本。
var async = function (func)
return function ()
var args = arguments;
setTimeout(function ()
func.apply(this, args);
, 0);
;
;
它被用作制作异步函数的简单方法:
var anyncFunction = async(function (callback)
doSomething();
callback();
);
这与@fider 的回答不同,因为函数本身有自己的结构(没有添加回调,它已经在函数中),还因为它创建了一个可以使用的新函数。
【讨论】:
setTimeout 不能在循环中使用(使用不同的参数多次调用同一个函数). @user2284570 这就是闭包的用途。(function(a) asyncFunction(a); )(a)
IIRC,你也可以在没有闭包的情况下实现这一点:setTimeout(asyncFunction, 0, a);
如果我们所说的异步是指:在后台运行,与主线程并行,那么这并不是真正的异步。所有这一切都会将执行延迟到 process.nextTick。函数中的任何代码都将在主线程上执行。如果该函数设置为计算 PI,则应用程序将冻结,无论是否超时!
我不明白为什么这个答案被赞成。当我把它放在我的代码中时,程序会阻塞直到函数完成,这正是它应该不做的事情。【参考方案5】:
编辑:我完全误解了这个问题。在浏览器中,我会使用setTimeout
。如果它在另一个线程中运行很重要,我会使用Web Workers。
【讨论】:
?这不会产生异步功能:O【参考方案6】:晚了,但是在 ES6 中介绍了 promises
之后,为了展示一个简单的解决方案,它可以更轻松地处理异步调用:
你在一个新的 Promise 中设置了异步代码:
var asyncFunct = new Promise(function(resolve, reject)
$('#link').animate( width: 200 , 2000, function()
console.log("finished");
resolve();
);
);
注意在异步调用完成时设置resolve()
。
然后在 promise 的 .then()
中添加要在异步调用完成后运行的代码:
asyncFunct.then((result) =>
console.log("Exit");
);
这是它的一个sn-p:
$('#link').click(function ()
console.log("Enter");
var asyncFunct = new Promise(function(resolve, reject)
$('#link').animate( width: 200 , 2000, function()
console.log("finished");
resolve();
);
);
asyncFunct.then((result) =>
console.log("Exit");
);
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" id="link">Link</a>
<span>Moving</span>
或JSFiddle
【讨论】:
据我了解,执行程序中的代码(new Promise)
的参数会立即运行,而不是在下一个刻度中运行。所以我不确定这个答案是否正确。但是,它看起来就像 then 处理程序总是在稍后的滴答声中运行。【参考方案7】:
This page 带您了解创建异步 javascript 函数的基础知识。
从 ES2017 开始,异步 javacript 函数更容易编写。您还应该在 Promises 上阅读更多内容。
【讨论】:
链接已失效。【参考方案8】:如果您想使用参数并调节异步函数的最大数量,您可以使用我构建的简单异步工作者:
var BackgroundWorker = function(maxTasks)
this.maxTasks = maxTasks || 100;
this.runningTasks = 0;
this.taskQueue = [];
;
/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params)
var self = this;
if(self.runningTasks >= self.maxTasks)
self.taskQueue.push( task: task, delay: delay, params: params);
else
self.runningTasks += 1;
var runnable = function(params)
try
task(params);
catch(err)
console.log(err);
self.taskCompleted();
// this approach uses current standards:
setTimeout(runnable, delay, params);
BackgroundWorker.prototype.taskCompleted = function()
this.runningTasks -= 1;
// are any tasks waiting in queue?
if(this.taskQueue.length > 0)
// it seems so! let's run it x)
var taskInfo = this.taskQueue.splice(0, 1)[0];
this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
你可以这样使用它:
var myFunction = function()
...
var myFunctionB = function()
...
var myParams = name: "John" ;
var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);
【讨论】:
【参考方案9】:Function.prototype.applyAsync = function(params, cb)
var function_context = this;
setTimeout(function()
var val = function_context.apply(undefined, params);
if(cb) cb(val);
, 0);
// usage
var double = function(n)return 2*n;;
var display = function()console.log(arguments); return undefined;;
double.applyAsync([3], display);
虽然与其他解决方案没有根本不同,但我认为我的解决方案还有一些额外的好处:
它允许为函数提供参数 它将函数的输出传递给回调 它被添加到Function.prototype
允许更好的调用方式
此外,与内置函数Function.prototype.apply
的相似性似乎对我来说很合适。
【讨论】:
【参考方案10】:在@pimvdb 的出色答案旁边,如果您想知道,async.js 也不提供真正的异步函数。这是库主要方法的(非常)精简版本:
function asyncify(func) // signature: func(array)
return function (array, callback)
var result;
try
result = func.apply(this, array);
catch (e)
return callback(e);
/* code ommited in case func returns a promise */
callback(null, result);
;
因此该函数可以防止错误并优雅地将其交给回调处理,但代码与任何其他 JS 函数一样同步。
【讨论】:
【参考方案11】:不幸的是,JavaScript 不提供异步功能。它仅适用于单个线程。但是大多数现代浏览器都提供Worker
s,这是在后台执行并可以返回结果的第二个脚本。
所以,我找到了一个解决方案,我认为异步运行一个函数很有用,它为每个异步调用创建一个工作线程。
下面的代码包含函数 async
在后台调用。
Function.prototype.async = function(callback)
let blob = new Blob([ "self.addEventListener('message', function(e) self.postMessage( result: (" + this + ").apply(null, e.data) ); , false);" ], type: "text/javascript" );
let worker = new Worker(window.URL.createObjectURL(blob));
worker.addEventListener("message", function(e)
this(e.data.result);
.bind(callback), false);
return function()
this.postMessage(Array.from(arguments));
.bind(worker);
;
这是一个使用示例:
(function(x)
for (let i = 0; i < 999999999; i++)
return x * 2;
).async(function(result)
alert(result);
)(10);
这将执行一个函数,该函数用一个巨大的数字迭代 for
以花时间作为异步性的演示,然后获取传递数字的双倍。
async
方法提供了一个function
,它在后台调用想要的函数,并且作为async
的参数提供的函数在其唯一参数中回调return
。
所以在回调函数中我alert
的结果。
【讨论】:
【参考方案12】:MDN 有一个good example 使用 setTimeout 保存“this”。
如下:
function doSomething()
// use 'this' to handle the selected element here
$(".someSelector").each(function()
setTimeout(doSomething.bind(this), 0);
);
【讨论】:
以上是关于如何在 Javascript 中创建异步函数?的主要内容,如果未能解决你的问题,请参考以下文章
你如何调用在javascript中的数组中创建的对象的函数?
你如何在 JavaScript 中创建一个等待函数,让你可以在后台运行音频之类的东西?
在useEffect中创建一个异步函数,并在函数的while循环中使用await,似乎不起作用
我们如何在 vuejs 的 javascript 文件中定义的 vuejs 的 html 文件中调用函数而不在 html 文件中创建按钮?