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


143

查看此代码

<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来源:)
yatskevich 2012年

.animate()方法使用回调。动画完成后,Animate将调用回调。如果您需要.animate()相同的行为,则需要一个回调(在其他一些操作之后由“ main”函数调用)。如果您需要一个“完整的”异步函数(一个不打扰执行流程的函数),则有所不同。在这种情况下,您可以将setTimeout()使用接近0的延迟。
法比奥·布达

@Fabio Buda:为什么callback()应该实现某种异步?实际上,它不会jsfiddle.net/5H9XT/9
markzzz 2012年

实际上,在“回调”之后,我引用了带有setTimeout的“完整”异步方法。我的意思是在其他代码之后调用回调的方式是伪异步:-)
Fabio Buda 2012年

Answers:


185

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

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

实际上,jQuery 使用 动画setInterval


1
我昨天和一个朋友讨论了这个问题,所以这个答案很完美!我了解并可以识别异步功能,并在JS中正确使用它们。但是,对于我们为什么不能实现自定义的原因,我仍然不清楚。这就像一个黑盒子,我们知道它是如何工作的(使用,例如setInterval),但是我们甚至无法打开它来查看它是如何完成的。您是否有关于该主题的更多信息?
Matheus Felipe'Mar

2
@MatheusFelipe这些函数是javascript引擎实现所固有的,并且您唯一可以依赖的就是规范,例如HTML5计时器,并信任它们根据规范表现的黑盒性质。
Spoike

10
@MatheusFelipe youtu.be/8aGhZQkoFbQ迄今为止关于该主题的最佳话题……
Andreas Niedermair 2015年

支持某些实现,尤其是Node.jssetImmediate
Jon Surrell 2015年

那呢promises?它给awaitable吗?
Nithin Chandran

69

您可以使用计时器:

setTimeout( yourFn, 0 );

(在哪里yourFn引用您的功能)

或者,使用Lodash

_.defer( yourFn );

延迟调用func直到清除当前调用堆栈。func调用时会提供任何其他参数。


3
这不起作用,我在画布中绘制的javascript函数使UI一直没有响应。
gab06年

2
@ gab06-我想说您的画布绘制功能由于其自身的良好原因而被阻止。拆分其行动统一到许多较小的和调用与定时器他们每个人都:你会看到界面这样做如何响应您的鼠标点击,等等
马可Faustinelli


1
@JulianSoto固定
森那维达斯

1
setTimeout根据HTML5规范,最短时间为4毫秒。设为0仍会花费最短的时间。但是,是的,它可以很好地用作功能延迟器。
hegez

30

在这里,您有简单的解决方案(其他请写一下) 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时执行-将模拟异步任务


6
TEST 1实际上只能输出“ 1 2 3 x”,并且TEST 2每次都保证输出“ 1 x”。在TEST 2中出现意外结果的原因是因为console.log(1)调用了并且输出(undefined)作为第二个参数传递给了async()。对于TEST 1,我认为您不完全了解JavaScript的执行队列。因为每个调用都console.log()发生在同一堆栈中,x所以保证最后记录一次。我会为错误信息否决此答案,但没有足够的代表。
Joshua Piccari 2015年

1
@Joshua:@fider似乎打算将TEST 2编写为:async(function() {console.log('x')}, function(){console.log(1)});
nzn 2015年

是的@nzn和@Joshua我的意思是TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});-我已经更正了
推手

在async(function(){setTimeout(()=> {console.log('x');},1000)},function(){console.log(1);})中,TEST 2输出为1 x
Mohsen

10

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

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

@ user2284570这就是闭包的目的。(function(a){ asyncFunction(a); })(a)
旋转

1
IIRC,您也可以不关闭而实现:setTimeout(asyncFunction, 0, a);
旋转

1
如果说异步是指:在后台运行,与主线程并行,那么这并不是真正的异步。这一切将延迟执行到process.nextTick。该函数中包含的所有代码都将在主线程上执行。如果该功能设置为计算PI,则该应用程序将冻结,无论超时与否!
Mike M

1
我不明白为什么要回答这个问题。当我把这个在我的代码,程序块,直至功能完成,这正是它应该不会做。
Martin Argerami


6

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

您以新的承诺设置了异步代码:

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

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

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



3

如果您想使用Parameters并调整异步函数的最大数量,则可以使用我构建的简单异步工作器:

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

2
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似乎对我来说很合适。


1

除了@pimvdb的出色答案之外,以防万一,您也想知道,async.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

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

下面的代码包含 在后台调用的函数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了结果。


0

MDN 在使用setTimeout保留“ this” 方面有一个很好的例子

如下所示:

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

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.