如何在 Javascript 中创建异步函数?

看看这个 密码:

<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");
});

正如您在控制台中看到的,“ animate”函数是异步的,而“ fork”是事件处理程序块代码的流。事实上:

$('#link').click(function () {
console.log("Enter");
asyncFunct();
console.log("Exit");
});


function asyncFunct() {
console.log("finished");
}

跟随块代码的流程!

如果我希望用这种行为创建我的 function asyncFunct() { },那么如何用 javascript/jquery 来实现呢?我认为有一个不使用 setTimeout()的策略

226102 次浏览

编辑: 我完全误解了这个问题。在浏览器中,我会使用 setTimeout。如果它在另一个线程中运行很重要,我会使用 网络工作者

你可以使用计时器:

setTimeout( yourFn, 0 );

(其中 yourFn是对函数的引用)

或者,与 Lodash一起:

_.defer( yourFn );

将调用 func的时间推迟到当前调用堆栈清除之后。当调用 func时,任何附加参数都被提供给它。

你不能创建一个真正自定义的异步函数,你最终必须利用本地提供的技术,比如:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • 某些 HTML5 API,如文件 API、 Web 数据库 API
  • 支持 onload的技术
  • 还有很多其他人

事实上,对于动画 jQuery 用途 setInterval

This page walks you through the basics of creating an async javascript function.

自 ES2017以来,异步 javaccript 函数更容易编写。您还应该阅读更多关于 我保证的内容。

在这里你有简单的解决方案(其他人写关于它) Http://www.benlesh.com/2012/05/calling-javascript-function.html

这里你有以上现成的解决方案:

function async(your_function, callback) {
setTimeout(function() {
your_function();
if (callback) {callback();}
}, 0);
}

测试1(可以输出’1 x 23’或’12 x 3’或’123 x’) :

console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);

测试2(将始终输出’x1’) :

async(function() {console.log('x');}, function() {console.log(1);});

这个函数执行时超时为0-它将模拟异步任务

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的相似性对我来说似乎是合适的。

MDN 在使用 setTimeout 时有一个 好榜样,保留“ this”。

如下所示:

function doSomething() {
// use 'this' to handle the selected element here
}


$(".someSelector").each(function() {
setTimeout(doSomething.bind(this), 0);
});

如果你想使用参数和调节异步函数的最大数量,你可以使用我构建的一个简单的异步工作器:

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);

下面是一个接受另一个函数并输出运行异步的版本的函数。

var async = function (func) {
return function () {
var args = arguments;
setTimeout(function () {
func.apply(this, args);
}, 0);
};
};

它被用作创建异步函数的一种简单方法:

var anyncFunction = async(function (callback) {
doSomething();
callback();
});

这与@fider 的答案不同,因为函数本身有自己的结构(没有添加回调,它已经在函数中了) ,还因为它创建了一个可以使用的新函数。

紧挨着@pilvdb 给出的最佳答案,如果您想知道的话,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 函数一样是同步的。

不幸的是,JavaScript 不提供异步功能。它只能在一个线程中工作。但是大多数现代浏览器都提供了 Worker是的,这是在后台执行并返回结果的第二个脚本。 因此,我得到了一个解决方案,我认为异步运行一个函数是有用的,它为每个异步调用创建一个 worker。

下面的代码包含在后台调用的函数 async < strong > 。

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以其唯一的参数回调所需的函数。 所以在回调函数 I alert中的结果。

虽然很晚,但是为了在 ES6中引入 promises之后展示一个简单的解决方案,它处理异步调用要容易得多:

您在一个新的承诺中设置了异步代码:

var asyncFunct = new Promise(function(resolve, reject) {
$('#link').animate({ width: 200 }, 2000, function() {
console.log("finished");
resolve();
});
});

当异步调用结束时,请注意设置 resolve()
然后,将要在异步调用完成后运行的代码添加到承诺的 .then()中:

asyncFunct.then((result) => {
console.log("Exit");
});

下面是其中的一个片段:

$('#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