Как я могу создать асинхронную функцию в 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");    
});

Как вы можете видеть в консоли, функция «animate» является асинхронной и «разветвляет» поток кода блока обработчика событий. По факту :

$('#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 после некоторых других операций). Другое дело, если вам нужна «полная» асинхронная функция (функция, вызываемая без блокирования потока выполнения). В этом случае вы можете использовать setTimeout () с почти нулевой задержкой.
Фабио Буда

@ Фабио Буда: почему callback () должен реализовывать своего рода асинхронность? На самом деле это не jsfiddle.net/5H9XT/9
markzzz

фактически после «обратного вызова» я привел «полный» асинхронный метод с setTimeout. Я имел в виду обратный вызов как псевдо-асинхронный способ вызова функции после другого кода :-)
Фабио Буда

Ответы:


185

Вы не можете создать действительно пользовательскую асинхронную функцию. В конечном итоге вам придется использовать технологии, предоставляемые изначально, такие как:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Некоторые HTML5 API, такие как File API, API Web Database
  • Технологии, которые поддерживают onload
  • ... многие другие

На самом деле для анимации использует jQuery setInterval.


1
Я обсуждал это с другом вчера, так что этот ответ идеален! Я понимаю и могу определить асинхронные функции и правильно использовать их в JS. Но просто почему мы не можем реализовать собственные, мне не понятно. Это как черный ящик, который мы знаем, как заставить его работать (используя, скажем, setInterval), но мы не можем даже открыть его, чтобы увидеть, как это делается. У вас есть больше информации по этому вопросу?
Матеус Фелипе

2
@MatheusFelipe эти функции являются родными для реализации движка javascript, и единственное, на что вы можете положиться, - это спецификации, например, таймеры HTML5 и доверять природе черного ящика, что они ведут себя в соответствии со спецификациями.
Спойк

10
@MatheusFelipe youtu.be/8aGhZQkoFbQ пока что лучше всего поговорить на эту тему ...
Andreas Niedermair

Некоторые реализации, в частности Node.js, поддерживаютsetImmediate
Джон Суррелл

что о promises. Это дает awaitable?
Нитин Чандран

69

Вы можете использовать таймер:

setTimeout( yourFn, 0 );

(где yourFnссылка на вашу функцию)

или с Лодашем :

_.defer( yourFn );

Откладывает вызов funcдо тех пор, пока текущий стек вызовов не очистится. Любые дополнительные аргументы предоставляются, funcкогда он вызывается.


3
Это не работает, моя функция JavaScript, которая рисует на холсте, продолжает заставлять пользовательский интерфейс не отвечать.
gab06

2
@ gab06 - Я бы сказал, что ваша функция рисования на холсте блокируется по собственным причинам. Разделите его действие на множество более мелких и вызовите каждый из них с помощью таймера: вы увидите, что интерфейс таким образом реагирует на щелчки мыши и т. Д.
Марко Фаустинелли,

Ссылка не работает.
JulianSoto

1
@JulianSoto исправлено
Шиме Видас

1
Минимальное время setTimeoutсоставляет 4 миллисекунды по спецификации HTML5. Задание 0 все равно займет это минимальное количество времени. Но да, он хорошо работает в качестве функции отложенного вызова.
хегез

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)вызывается и output ( undefined) передается в качестве 2-го аргумента async(). В случае TEST 1, я думаю, вы не до конца понимаете очередь выполнения JavaScript. Поскольку каждый из вызовов console.log()происходит в одном стеке, xон гарантированно записывается последним. Я бы отклонил этот ответ за дезинформацию, но мне не хватило бы репутации.
Джошуа Пиккари

1
@Joshua: Кажется @fider предназначен для записи TEST 2 , как: async(function() {console.log('x')}, function(){console.log(1)});.
nzn

Да @nzn и @ Джошуа я имел в виду TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- я уже исправил это
fider

Выход TEST 2 равен 1 x в асинхронном режиме (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Мохсен

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

1
Если под асинхронностью мы имеем в виду: работать в фоновом режиме, параллельно основному потоку, то это не совсем асинхронно. Все, что нужно будет сделать, это отложить выполнение до process.nextTick. Какой бы код у вас ни был в функции, он будет выполняться в основном потоке. Если функция была установлена ​​для расчета PI, то приложение будет зависать с таймаутом или без него!
Майк М

1
Я не понимаю, почему за этот ответ проголосовали. Когда я кладу это в моем коде, программные блоки , пока функция закончена, что именно то , что он должен не делать.
Мартин Аргерами

6

Редактировать: я совершенно не понял вопрос. В браузере я бы использовал setTimeout. Если бы было важно, чтобы он запускался в другом потоке, я бы использовал Web Workers .


1
? Это не делает асинхронную функцию: O
markzzz

6

Поздно, но покажу простое решение, использующее promisesпосле их введения в ES6 , оно намного проще обрабатывает асинхронные вызовы:

Вы устанавливаете асинхронный код в новом обещании:

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



3

Если вы хотите использовать параметры и регулировать максимальное количество асинхронных функций, вы можете использовать простой асинхронный рабочий, который я построил:

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 также не предлагает действительно асинхронных функций. Вот (очень) урезанная версия основного метода библиотеки:

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);
});
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.