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

How can I create an Asynchronous function in Javascript?

提问人:markzzz 提问时间:3/1/2012 最后编辑:Vipul Sharmamarkzzz 更新时间:4/24/2019 访问量:229943

问:

看看这个代码

<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”函数是异步的,它“分叉”了事件处理程序块代码的流。事实上:

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

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

遵循块代码的流程!

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

JavaScript jQuery 函数 异步

评论

0赞 yatskevich 3/1/2012
看看jQuery源码:)
0赞 Fabio Buda 3/1/2012
.animate() mathod 使用回调。动画完成后,Animate 将调用回调。如果你需要与 .animate() 相同的行为,你需要的是一个回调(在一些其他操作后由“main”函数调用)。如果您需要一个“完整”异步函数(在不阻塞执行流的情况下调用的函数),则情况有所不同。在这种情况下,您可以使用延迟接近 0 的 setTimeout()。
0赞 markzzz 3/1/2012
@Fabio Buda : 为什么 callback() 应该实现一种异步?事实上,它并不 jsfiddle.net/5H9XT/9
0赞 Fabio Buda 3/1/2012
事实上,在“callback”之后,我引用了带有 setTimeout 的“完整”异步方法。我的意思是在其他代码之后调用函数的方式将回调作为伪异步:-)

答:

6赞 Linus Thiel 3/1/2012 #1

编辑:我完全误解了这个问题。在浏览器中,我会使用 .如果它在另一个线程中运行很重要,我会使用 Web WorkersetTimeout

评论

1赞 markzzz 3/1/2012
?这不会产生异步函数:O
74赞 Šime Vidas 3/1/2012 #2

您可以使用计时器:

setTimeout( yourFn, 0 );

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

或者,使用 Lodash

_.defer( yourFn );

将调用推迟到当前调用堆栈清除为止。调用时将提供任何其他参数。funcfunc

评论

3赞 gab06 7/26/2015
这是行不通的,我在画布中绘制的 javascript 函数一直使 UI 没有响应。
4赞 Marco Faustinelli 10/31/2015
@gab06 - 我想说你的画布绘图功能被阻塞是有其自身原因的。将它的动作分成许多较小的动作,并用计时器调用每个动作:你会看到这种方式的界面确实会响应你的鼠标点击,等等。
1赞 hegez 6/14/2018
根据 HTML5 规范,最短时间为 4 毫秒。给它 0 仍然需要最短的时间。但是,是的,它作为功能延迟器效果很好。setTimeout
0赞 AlexMelw 8/17/2020
对于函数,如果省略参数,则默认使用值 。scope.setTimeoutdelay0
191赞 pimvdb 3/1/2012 #3

您无法创建真正自定义的异步函数。您最终将不得不利用本机提供的技术,例如:

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

实际上,对于动画jQuery使用的.setInterval

评论

3赞 Matheus Felipe 3/28/2015
我昨天和一个朋友讨论这个问题,所以这个答案是完美的!我理解并能够识别异步函数并在 JS 中正确使用它们。但是,我不清楚为什么我们不能实现自定义的。这就像一个黑匣子,我们知道如何让它工作(比如说,),但我们甚至无法打开它来查看它是如何完成的。你碰巧有更多关于这个主题的信息吗?setInterval
2赞 Spoike 5/1/2015
@MatheusFelipe这些函数是 javascript 引擎实现的原生函数,您唯一可以依赖的是规范,例如 HTML5 计时器,并相信它们根据规范运行的黑盒性质。
10赞 8/3/2015
@MatheusFelipe迄今为止关于这个话题 youtu.be/8aGhZQkoFbQ 最好的演讲......
0赞 Jon Surrell 11/12/2015
一些实现,特别是 Node.js,支持 setImmediate
3赞 Beingnin 2/3/2020
怎么样。它给一个 ?promisesawaitable
5赞 shanabus 3/1/2012 #4

本页将指导您完成创建异步 javascript 函数的基础知识。

从 ES2017 开始,异步 javacript 函数的编写要容易得多。您还应该阅读更多关于 Promise 的信息

评论

0赞 Martin Argerami 4/20/2019
链接已失效。
31赞 fider 6/28/2013 #5

在这里,您有简单的解决方案(其他写它)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 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);

测试 2(将始终输出 'x 1'):

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

此函数在超时 0 时执行 - 它将模拟异步任务

评论

7赞 Joshua Piccari 3/9/2015
TEST 1 实际上只能输出 '1 2 3 x',而 TEST 2 保证每次都输出 '1 x'。TEST 2 中出现意外结果的原因是,调用了 ,并且输出 () 作为第二个参数传递给 。在测试 1 的情况下,我认为您并不完全了解 JavaScript 的执行队列。因为每个调用都发生在同一个堆栈中,所以保证最后记录。我会因为错误信息而否决这个答案,但没有足够的代表。console.log(1)undefinedasync()console.log()x
1赞 nzn 6/4/2015
@Joshua:似乎@fider打算将测试 2 写成:.async(function() {console.log('x')}, function(){console.log(1)});
0赞 fider 6/8/2015
是的,@nzn,我的意思是@Joshua - 我已经纠正了它TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});
0赞 Mohsen 4/4/2018
TEST 2 输出为 1 x in async(function() {setTimeout(()=>{console.log('x');},1000)}, function() {console.log(1);});
2赞 Benjamin Kuykendall 8/3/2015 #6
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

0赞 xtian 10/17/2015 #7

MDN 有一个很好的例子,使用 setTimeout 来保留 “this”。

如下所示:

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

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
3赞 João Alves 11/12/2015 #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);
10赞 Ethan McTague 11/15/2015 #9

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

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

它被用作制作异步函数的简单方法:

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

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

评论

0赞 user2284570 5/29/2016
setTimeout 不能在循环中使用(使用不同的参数多次调用同一个函数)。
0赞 Swivel 11/23/2016
@user2284570 这就是闭合的用途。(function(a){ asyncFunction(a); })(a)
1赞 Swivel 11/23/2016
IIRC,您也可以在不关闭的情况下实现此目的:setTimeout(asyncFunction, 0, a);
1赞 Mike M 8/4/2017
如果异步是指:在后台运行,与主线程并行,那么这不是真正的异步。所有这一切都将延迟执行到 process.nextTick。函数中的任何代码都将在主线程上执行。如果该函数设置为计算 PI,则应用程序将冻结,无论是否超时!
1赞 Martin Argerami 4/21/2019
我不明白为什么这个答案被点赞。当我把它放在我的代码中时,程序会阻塞,直到函数完成,这正是它不应该做的。
1赞 Mike M 8/4/2017 #10

除了 @pimvdb 的出色答案之外,以防万一您想知道,异步.js 也没有提供真正的异步函数。这是库 main 方法的(非常)精简版本:

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 函数一样同步。

1赞 Davide Cannizzo 11/12/2017 #11

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

下面的代码包含要在后台调用的函数。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);

这执行一个函数,该函数用巨大的数字迭代 a,以花时间作为异步性的演示,然后得到传递数字的双倍。 该方法提供了一个在后台调用所需函数的函数,并在其唯一参数中作为回调的参数提供。 所以在回调函数中,我的结果。forasyncfunctionasyncreturnalert

6赞 hd84335 12/31/2017 #12

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

在新 promise 中设置异步代码:

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

注意设置异步调用完成的时间。
然后,在 promise 中添加要在异步调用完成后运行的代码:
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

评论

1赞 Sam Hartman 9/23/2020
据我了解,执行器中的代码(参数 to 立即运行,而不是在下一个刻度中。所以我不确定这个答案是否正确。但是,看起来 then 处理程序总是在稍后的 tick 中运行。new Promise)