Jak mogę utworzyć funkcję asynchroniczną w JavaScript?

143

Sprawdź ten kod :

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

Jak widać w konsoli, funkcja „animate” jest asynchroniczna i „rozwidla” przepływ kodu blokowego modułu obsługi zdarzeń. W rzeczywistości :

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

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

podążaj za przepływem kodu blokowego!

Jeśli chcę utworzyć plik function asyncFunct() { } z takim zachowaniem, jak mogę to zrobić za pomocą javascript / jquery? Myślę, że jest to strategia bez użycia setTimeout()

markzzz
źródło
spójrz na źródła jQuery :)
yatskevich
Mathod .animate () używa wywołania zwrotnego. Program Animate wywoła wywołanie zwrotne po zakończeniu animacji. Jeśli potrzebujesz tego samego zachowania .animate (), potrzebujesz wywołania zwrotnego (wywoływanego przez funkcję "main" po kilku innych operacjach). Jest inaczej, jeśli potrzebujesz „pełnej” funkcji asynchronicznej (funkcja o nazwie bez blokowania przepływu wykonywania). W tym przypadku możesz użyć setTimeout () z opóźnieniem bliskim 0.
Fabio Buda
@Fabio Buda: dlaczego callback () ma implementować coś w rodzaju asynchronii? W rzeczywistości nie działa jsfiddle.net/5H9XT/9
markzzz
w rzeczywistości po „callback” zacytowałem „pełną” metodę asynchroniczną z setTimeout. Miałem na myśli callback jako pseudo-async w sposobie wywoływania funkcji po innym kodzie :-)
Fabio Buda

Odpowiedzi:

185

Nie można utworzyć prawdziwie niestandardowej funkcji asynchronicznej. W końcu będziesz musiał wykorzystać technologię dostarczaną natywnie, taką jak:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Niektóre interfejsy API HTML5, takie jak File API, Web Database API
  • Technologie, które wspierają onload
  • ... wiele innych

W rzeczywistości do animacji jQuery używa setInterval .

pimvdb
źródło
1
Wczoraj omawiałem to z przyjacielem, więc ta odpowiedź jest idealna! Rozumiem i potrafię zidentyfikować funkcje asynchroniczne i prawidłowo z nich korzystać w JS. Ale po prostu, dlaczego nie możemy wdrożyć niestandardowych, nie jest dla mnie jasne. To jest jak czarna skrzynka, którą wiemy, jak to działa (powiedzmy, używając setInterval), ale nie możemy jej nawet otworzyć, aby zobaczyć, jak to się robi. Masz więcej informacji na ten temat?
Matheus Felipe
2
@MatheusFelipe te funkcje są natywne dla implementacji silnika javascript i jedyne, na czym możesz polegać, to specyfikacje, np. Timery HTML5 i ufaj naturze czarnej skrzynki, że zachowują się zgodnie ze specyfikacjami.
Spoike
10
@MatheusFelipe youtu.be/8aGhZQkoFbQ najlepszy talk dotychczas dotyczące tego tematu ...
Andreas Niedermair
Niektóre implementacje, w szczególności Node.js, obsługująsetImmediate
Jon Surrell,
co z promises. Czy to daje awaitable?
Nithin Chandran
69

Możesz użyć timera:

setTimeout( yourFn, 0 );

(gdzie yourFnjest odniesienie do twojej funkcji)

lub z Lodash :

_.defer( yourFn );

Odracza wywoływanie do funcmomentu wyczyszczenia bieżącego stosu wywołań. Wszelkie dodatkowe argumenty są podawane funcpodczas wywołania.

Šime Vidas
źródło
3
To nie działa, moja funkcja javascript, która rysuje na płótnie, powoduje, że interfejs użytkownika nie odpowiada.
gab06
2
@ gab06 - Powiedziałbym, że funkcja rysowania na płótnie blokuje się z własnych ważnych powodów. Podziel jego akcję na wiele mniejszych i wywołaj każdą z nich za pomocą timera: zobaczysz, że interfejs w ten sposób reaguje na kliknięcia myszą itp.
Marco Faustinelli
Link jest uszkodzony.
JulianSoto
1
@JulianSoto naprawiono
Šime Vidas
1
Minimalny czas setTimeoutto 4 milisekundy według specyfikacji HTML5. Nadanie mu 0 nadal zajmie minimalną ilość czasu. Ale tak, działa dobrze jako odrocznik funkcji.
hegez
30

tutaj masz proste rozwiązanie (inni o tym piszą) http://www.benlesh.com/2012/05/calling-javascript-function.html

A oto gotowe rozwiązanie:

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

TEST 1 ( może wyświetlać '1 x 2 3' lub '1 2 x 3' lub '1 2 3 x' ):

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

TEST 2 ( zawsze wyświetla 'x 1' ):

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

Ta funkcja jest wykonywana z limitem czasu 0 - będzie symulować zadanie asynchroniczne

fider
źródło
6
TEST 1 może właściwie wyprowadzić tylko „1 2 3 x”, a TEST 2 gwarantuje, że za każdym razem wyprowadzi „1 x”. Przyczyną nieoczekiwanych wyników w TEST 2 jest to, że console.log(1)jest wywoływana, a output ( undefined) jest przekazywany jako drugi argument do async(). W przypadku TESTU 1 myślę, że nie w pełni rozumiesz kolejkę wykonywania JavaScript. Ponieważ każde wywołanie, które ma console.log()miejsce na tym samym stosie, xjest rejestrowane jako ostatnie. Głosowałbym w dół na tę odpowiedź za dezinformację, ale nie mam wystarczającej liczby powtórzeń.
Joshua Piccari,
1
@Joshua: Wydaje @fider oznaczało napisać test 2 jak: async(function() {console.log('x')}, function(){console.log(1)});.
nzn
Tak @nzn i @Joshua Miałem na myśli TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- już to poprawiłem
fider
Wyjście TESTU 2 to 1 x w async (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen
10

Oto funkcja, która przyjmuje inną funkcję i wyprowadza wersję, która działa asynchronicznie.

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

Jest używany jako prosty sposób na utworzenie funkcji asynchronicznej:

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

Różni się to od odpowiedzi @ fider, ponieważ sama funkcja ma swoją własną strukturę (bez dodanego wywołania zwrotnego, jest już w funkcji), a także dlatego, że tworzy nową funkcję, której można użyć.

Ethan McTague
źródło
setTimeout nie może być używany w pętli (wywołaj tę samą funkcję kilka razy z różnymi argumentami) .
user2284570
@ user2284570 Po to są domknięcia. (function(a){ asyncFunction(a); })(a)
Swivel
1
IIRC, możesz to również osiągnąć bez zamknięcia:setTimeout(asyncFunction, 0, a);
Swivel
1
Jeśli przez asynchroniczność rozumiemy: działanie w tle, równolegle do głównego wątku, to nie jest to naprawdę asynchroniczne. Wszystko to spowoduje opóźnienie wykonania do procesu .nextTick. Kod, który masz w funkcji, zostanie wykonany w głównym wątku. Jeśli funkcja została ustawiona na obliczanie PI, aplikacja zawiesi się, z przekroczeniem limitu czasu lub bez!
Mike M
1
Nie rozumiem, dlaczego ta odpowiedź jest przegłosowana. Kiedy kładę to w moim kodzie, program blokuje aż funkcja jest zakończona, co dokładnie to co powinno nie robić.
Martin Argerami
6

Edycja: całkowicie źle zrozumiałem pytanie. W przeglądarce użyłbym setTimeout. Gdyby ważne było, aby działał w innym wątku, użyłbym Web Workers .

Linus Thiel
źródło
1
? To nie tworzy funkcji asynchronicznej: O
markzzz
6

Późno, ale aby pokazać proste rozwiązanie promisespo ich wprowadzeniu w ES6 , o wiele łatwiej obsługuje wywołania asynchroniczne:

Ustawiasz kod asynchroniczny w nowej obietnicy:

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

Uwaga, aby ustawić, resolve()kiedy zakończy się połączenie asynchroniczne.
Następnie dodajesz kod, który chcesz uruchomić po zakończeniu wywołania asynchronicznego wewnątrz .then()obietnicy:

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

Oto jego fragment:

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

lub JSFiddle

hd84335
źródło
6

Ta strona przeprowadzi Cię przez podstawy tworzenia asynchronicznej funkcji javascript.

Od ES2017 asynchroniczne funkcje javacript są znacznie łatwiejsze do napisania. Powinieneś także przeczytać więcej na Obietnice .

shanabus
źródło
Link nie działa.
Martin Argerami
3

Jeśli chcesz używać parametrów i regulować maksymalną liczbę funkcji asynchronicznych, możesz użyć prostego pracownika asynchronicznego, który zbudowałem:

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

Możesz go używać w ten sposób:

var myFunction = function() {
 ...
}
var myFunctionB = function() {
 ...
}
var myParams = { name: "John" };

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);
João Alves
źródło
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);

Chociaż nie różni się zasadniczo od innych rozwiązań, myślę, że moje rozwiązanie ma kilka dodatkowych fajnych rzeczy:

  • pozwala na parametry funkcji
  • przekazuje dane wyjściowe funkcji do wywołania zwrotnego
  • dodaje się to do Function.prototypeumożliwienia ładniejszego nazwania tego

Również podobieństwo do funkcji wbudowanej Function.prototype.applywydaje mi się odpowiednie.

Benjamin Kuykendall
źródło
1

Oprócz świetnej odpowiedzi @pimvdb i na wypadek, gdybyś się zastanawiał, async.js również nie oferuje prawdziwie asynchronicznych funkcji. Oto (bardzo) uproszczona wersja głównej metody biblioteki:

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

Tak więc funkcja chroni przed błędami i wdzięcznie przekazuje ją do wywołania zwrotnego do obsługi, ale kod jest tak samo synchroniczny jak każda inna funkcja JS.

Mike M.
źródło
1

Niestety JavaScript nie zapewnia funkcji asynchronicznej. Działa tylko w jednym wątku. Ale większość nowoczesnych przeglądarek zapewnia Workers , czyli drugie skrypty, które są wykonywane w tle i mogą zwracać wynik. Tak więc doszedłem do rozwiązania, które uważam za przydatne do asynchronicznego uruchamiania funkcji, która tworzy proces roboczy dla każdego wywołania asynchronicznego.

Poniższy kod zawiera funkcję async do wywołania w tle.

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

Oto przykład użycia:

(function(x) {
    for (let i = 0; i < 999999999; i++) {}
        return x * 2;
}).async(function(result) {
    alert(result);
})(10);

To wykonuje funkcję, która iteruje a forz ogromną liczbą, aby zająć trochę czasu jako demonstracja asynchroniczności, a następnie pobiera podwojenie podanej liczby. asyncMetoda zapewnia functionktóra wywołuje żądaną funkcję w tle, a to, co jest przewidziane jako parametr asyncwywołania zwrotne ze returnw jego unikalny parametr. Więc w funkcji zwrotnej alertotrzymuję wynik.

Davide Cannizzo
źródło
0

MDN ma dobry przykład użycia setTimeout zachowującego „this”.

Jak poniżej:

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

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
xtian
źródło