Como posso criar uma função assíncrona em Javascript?

143

Confira este código :

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

Como você pode ver no console, a função "animar" é assíncrona e "bifurca" o fluxo do código de bloco do manipulador de eventos. De fato :

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

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

siga o fluxo do código de bloco!

Se eu quiser criar o meu function asyncFunct() { }com esse comportamento, como posso fazê-lo com javascript / jquery? Eu acho que há uma estratégia sem o uso de setTimeout()

markzzz
fonte
dar uma olhada em fontes jQuery :)
yatskevich
O mathod .animate () usa um retorno de chamada. O Animate chamará o retorno de chamada quando a animação estiver concluída. Se você precisar do mesmo comportamento de .animate (), precisará de um retorno de chamada (chamado pela função "main" após algumas outras operações). É diferente se você precisar de uma função assíncrona "completa" (uma função chamada sem bloquear o fluxo de execução). Nesse caso, você pode usar setTimeout () com um atraso próximo de 0.
Fabio Buda
@ Fabio Buda: por que callback () deve implementar uma espécie de assíncrono? Na verdade, isso não acontecer jsfiddle.net/5H9XT/9
markzzz
de fato, após "retorno de chamada", citei um método assíncrono "completo" com setTimeout. Eu quis dizer callback como pseudo-assíncrona na forma como a função é chamada após o outro código :-)
Fabio Buda

Respostas:

185

Você não pode criar uma função assíncrona verdadeiramente personalizada. Eventualmente, você terá que aproveitar uma tecnologia fornecida nativamente, como:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Algumas APIs HTML5, como a API de arquivos, a API de banco de dados da Web
  • Tecnologias que suportam onload
  • ... muitos outros

De fato, para a animação que o jQuery usa setInterval .

pimvdb
fonte
1
Eu estava discutindo isso com um amigo ontem, então essa resposta é perfeita! Entendo e posso identificar as funções assíncronas e usá-las corretamente em JS. Mas simplesmente por que não podemos implementar os personalizados não está claro para mim. É como uma caixa preta que sabemos como fazê-la funcionar (usando, digamos setInterval) , mas que não podemos nem abri-la para ver como é feita. Você tem mais informações sobre o assunto?
Matheus Felipe
2
@MatheusFelipe essas funções são nativas da implementação do mecanismo javascript e a única coisa em que você pode confiar são nas especificações, por exemplo , temporizadores HTML5 e confiar na natureza da caixa preta em que se comportam de acordo com as especificações.
Spoike
10
@MatheusFelipe youtu.be/8aGhZQkoFbQ melhor talk até agora sobre este tema ...
Andreas Niedermair
Alguns implemantations, em particular Node.js, suportesetImmediate
Jon Surrell
que tal promises. Dá um awaitable?
Nithin Chandran
69

Você pode usar um timer:

setTimeout( yourFn, 0 );

(onde yourFné uma referência à sua função)

ou, com Lodash :

_.defer( yourFn );

Adia a chamada funcaté que a pilha de chamadas atual seja limpa. Quaisquer argumentos adicionais são fornecidos funcquando são chamados.

Šime Vidas
fonte
3
Isso não funciona, minha função javascript que desenha em uma tela continua fazendo a interface do usuário não responder.
26615 gab06
2
@ gab06 - Eu diria que sua função de desenho de tela está bloqueando por seus próprios motivos. Dividir a sua ação em muitos menores e invocar cada um deles com um temporizador: você verá que a interface desta maneira não responde a seus cliques do mouse, etc.
Marco Faustinelli
Link quebrado.
JulianSoto
1
@JulianSoto corrigido
Šime Vidas
1
O tempo mínimo para setTimeouté de 4 milissegundos por especificação do HTML5. Dar 0 ainda levará esse tempo mínimo. Mas sim, funciona bem como um adiamento de funções.
22618 hehez
30

aqui você tem uma solução simples (outros escrevem sobre isso) http://www.benlesh.com/2012/05/calling-javascript-function.html

E aqui você tem a solução pronta acima:

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

TESTE 1 ( pode gerar '1 x 2 3' ou '1 2 x 3' ou '1 2 3 x' ):

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

TESTE 2 ( sempre gera 'x 1' ):

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

Esta função é executada com o tempo limite 0 - simulará tarefas assíncronas

corredor
fonte
6
Na verdade, o TESTE 1 só pode gerar '1 2 3 x' e o TESTE 2 é garantido como '1 x' toda vez. O motivo dos resultados inesperados no TESTE 2 é porque console.log(1)é chamado e a saída ( undefined) é passada como o segundo argumento para async(). No caso do TESTE 1, acho que você não entende completamente a fila de execução do JavaScript. Como cada uma das chamadas console.log()ocorre na mesma pilha, xé garantido que será registrado por último. Eu votaria negativamente nesta resposta por desinformação, mas não tenho representante suficiente.
Joshua Piccari
1
@Joshua: Parece @fider pretendia escrever TEST 2 como: async(function() {console.log('x')}, function(){console.log(1)});.
Nzn 4/06
Sim @nzn e @ Josué, eu quis dizer TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- já o corrigi
#
A saída do TESTE 2 é 1 x em assíncrona (função () {setTimeout (() => {console.log ('x');}, 1000)}), function () {console.log (1);});
Mohsen
10

Aqui está uma função que assume outra função e gera uma versão que executa assíncrona.

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

É usado como uma maneira simples de criar uma função assíncrona:

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

Isso é diferente da resposta do @ fider porque a função em si tem sua própria estrutura (nenhum retorno de chamada foi adicionado, já está na função) e também porque cria uma nova função que pode ser usada.

Ethan McTague
fonte
setTimeout não pode ser usado em um loop (chame a mesma função várias vezes com argumentos distintos) .
User2284570
@ user2284570 É para isso que servem os fechamentos. (function(a){ asyncFunction(a); })(a)
giratória
1
IIRC, você também pode conseguir isso sem um fechamento:setTimeout(asyncFunction, 0, a);
Swivel
1
Se por async queremos dizer: rodando em segundo plano, paralelo ao thread principal, isso não é verdadeiramente assíncrono. Tudo o que isso fará é atrasar a execução para o process.nextTick. Qualquer código que você tenha na função será executado no thread principal. Se a função foi definida para calcular o PI, o aplicativo irá congelar, com ou sem tempo limite!
Mike M
1
Não entendo por que essa resposta foi votada. Quando eu coloco isso no meu código, o programa bloqueia até que a função seja concluída, o que exatamente não deve ser feito.
Martin Argerami
6

Edit: Eu entendi totalmente a pergunta. No navegador, eu usaria setTimeout. Se fosse importante que fosse executado em outro segmento, eu usaria Web Workers .

Linus Thiel
fonte
1
? Isso não
cria
6

Tarde, mas para mostrar uma solução fácil promisesapós a introdução no ES6 , ele lida com chamadas assíncronas muito mais fácil:

Você define o código assíncrono em uma nova promessa:

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

Nota para definir resolve()quando a chamada assíncrona termina.
Em seguida, adicione o código que deseja executar após a conclusão da chamada assíncrona dentro .then()da promessa:

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

Aqui está um trecho:

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

ou JSFiddle

hd84335
fonte
6

Esta página mostra os conceitos básicos da criação de uma função javascript assíncrona.

Desde o ES2017, as funções assíncronas de javacript são muito mais fáceis de escrever. Você também deve ler mais sobre promessas .

Shanabus
fonte
O link está morto.
Martin Argerami
3

Se você deseja usar Parâmetros e regular o número máximo de funções assíncronas, pode usar um trabalhador assíncrono simples que eu construí:

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

Você pode usá-lo assim:

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

Embora não seja fundamentalmente diferente das outras soluções, acho que minha solução faz mais algumas coisas legais:

  • permite parâmetros para as funções
  • passa a saída da função para o retorno de chamada
  • é adicionado a Function.prototypepermitir uma maneira melhor de chamá-lo

Além disso, a semelhança com a função Function.prototype.applyinterna parece apropriada para mim.

Benjamin Kuykendall
fonte
1

Ao lado da ótima resposta de @pimvdb, e no caso de você estar se perguntando, o async.js também não oferece funções verdadeiramente assíncronas. Aqui está uma versão (muito) simplificada do método principal da biblioteca:

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

Portanto, a função protege contra erros e a entrega normalmente ao retorno de chamada, mas o código é tão síncrono quanto qualquer outra função JS.

Mike M
fonte
1

Infelizmente, o JavaScript não fornece uma funcionalidade assíncrona. Funciona apenas em um único segmento. Mas a maioria dos navegadores modernos fornece Workers , que são segundos scripts que são executados em segundo plano e podem retornar um resultado. Então, cheguei a uma solução que acho útil executar de forma assíncrona uma função, que cria um trabalhador para cada chamada assíncrona.

O código abaixo contém a função async para chamar em segundo plano.

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

Este é um exemplo de uso:

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

Isso executa uma função que itera um fornúmero enorme para levar tempo como demonstração de assincronicidade e, em seguida, obtém o dobro do número passado. O asyncmétodo fornece um functionque chama a função desejada em segundo plano e o que é fornecido como parâmetro de asyncretornos de chamada returnem seu parâmetro exclusivo. Então, na função de retorno de chamada, alerto resultado.

Davide Cannizzo
fonte
0

O MDN tem um bom exemplo do uso de setTimeout preservando "this".

Como o seguinte:

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

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
xtian
fonte