Як я можу створити асинхронну функцію в 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");    
});

Як ви бачите на консолі, функція "anime" є асинхронною, і вона "розщеплює" потік блоку коду обробника подій. Фактично :

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

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

слідкуйте за потоком блочного коду!

Якщо я хочу створити свою function asyncFunct() { }поведінку, як це зробити за допомогою JavaScript / jquery? Я думаю , що є стратегія без використання setTimeout()


погляньте на джерела jQuery :)
яцкевич

.Animate () mathod використовує зворотний виклик. Анімація викликає зворотний виклик, коли анімація завершена. Якщо вам потрібна така сама поведінка .animate (), вам потрібен зворотний виклик (викликається функцією "main" після деяких інших операцій). Це інакше, якщо вам потрібна "повна" функція асинхронізації (функція, яка називається без блокування потоку виконання). У цьому випадку ви можете використовувати setTimeout () із затримкою майже 0.
Фабіо Буда

@ Фабіо Буда: чому callback () повинен реалізувати своєрідну асинхронізацію? Насправді, це не jsfiddle.net/5H9XT/9
markzzz

насправді після "зворотного виклику" я цитував "повний" метод асинхронізації з setTimeout. Я мав на увазі зворотний виклик як псевдо-асинхронний спосіб виклику функції після іншого коду :-)
Фабіо Буда

Відповіді:


185

Ви не можете зробити справді власну асинхронну функцію. Зрештою, вам доведеться скористатися технологією, що надається на зразок:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Деякі API API HTML5, такі як API файлів, API веб-баз даних
  • Технології, що підтримують 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 - Я б сказав, що ваша функція малювання полотна блокується з власних вагомих причин. Розподіліть свою дію на багато менших і викликайте кожен з них таймером: ви побачите, що інтерфейс таким чином реагує на натискання вашої миші тощо
Marco Faustinelli

Посилання розірвано.
JulianSoto

1
@JulianSoto виправлено
Šime

1
Мінімальний час setTimeout- 4 мілісекунди за специфікацією HTML5. Якщо надати 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);
}

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 - вона буде імітувати асинхронну задачу


6
TEST 1 насправді може виводити лише "1 2 3 x", а TEST 2 гарантовано виводить "1 x" кожен раз. Причина несподіваних результатів у TEST 2 полягає в тому console.log(1), що викликається, а результат ( undefined) передається як другий аргумент async(). У випадку TEST 1, я думаю, ви не повністю розумієте чергу виконання JavaScript. Оскільки кожен з дзвінків console.log()відбуватиметься в одній стеці, xгарантовано буде останнім. Я б відповідав за дезінформацію, але не маю достатньої кількості представників.
Джошуа Піккарі

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

Так @nzn і @Joshua я мав на увазі TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- я це вже виправив
fider

Вихід TEST 2 - 1 x в async (функція () {setTimeout (() => {console.log ('x');}, 1000)}, функція () {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 не можна використовувати в циклі (викликайте одну і ту ж функцію кілька разів з різними аргументами) .
користувач2284570

@ user2284570 Ось для чого і закриття. (function(a){ asyncFunction(a); })(a)
Поворот

1
IIRC, ви могли також досягти цього без закриття:setTimeout(asyncFunction, 0, a);
Поворот

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

Це виконує функцію, яка повторює a forз величезною кількістю, щоб зайняти час як демонстрацію асинхронності, а потім отримує вдвічі перевищене число. asyncМетод забезпечує , functionякий викликає потрібну функцію в фоновому режимі, і в тому , що зазначена в якості параметра asyncфункції зворотного виклику в returnв його єдиному параметрі. Отже, у функції зворотного виклику я отримую alertрезультат.


0

MDN має хороший приклад використання setTimeout із збереженням "цього".

Як і наступне:

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.