可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have two JS functions. One calls the other. Within the calling function, I'd like to call the other, wait for that function to finish, then continue on. So, for example/pseudo code:
function firstFunction(){
for(i=0;i<x;i++){
// do something
}
};
function secondFunction(){
firstFunction()
// now wait for firstFunction to finish...
// do something else
};
I came up with this solution, but don't know if this is a smart way to go about it.
var isPaused = false;
function firstFunction(){
isPaused = true;
for(i=0;i<x;i++){
// do something
}
isPaused = false;
};
function secondFunction(){
firstFunction()
function waitForIt(){
if (isPaused) {
setTimeout(function(){waitForIt()},100);
} else {
// go do that thing
};
}
};
Is that legit? Is there a more elegant way to handle it? Perhaps with jQuery?
回答1:
One way to deal with asynchronous work like this is to use a callback function, eg:
function firstFunction(_callback){
// do some asynchronous work
// and when the asynchronous stuff is complete
_callback();
}
function secondFunction(){
// call first function and pass in a callback function which
// first function runs when it has completed
firstFunction(function() {
console.log('huzzah, I\'m done!');
});
}
As per @Janaka Pushpakumara's suggestion, you can now use arrow functions to achieve the same thing. For example:
firstFunction(() => console.log('huzzah, I\'m done!'))
回答2:
It appears you're missing an important point here: JavaScript is a single-threaded execution environment. Let's look again at your code, note I've added alert("Here")
:
var isPaused = false;
function firstFunction(){
isPaused = true;
for(i=0;i<x;i++){
// do something
}
isPaused = false;
};
function secondFunction(){
firstFunction()
alert("Here");
function waitForIt(){
if (isPaused) {
setTimeout(function(){waitForIt()},100);
} else {
// go do that thing
};
}
};
You don't have to wait for isPaused
. When you see the "Here" alert, isPaused
will be false
already, and firstFunction
will have returned. That's because you cannot "yield" from inside the for
loop (// do something
), the loop may not be interrupted and will have to fully complete first (more details: Javascript thread-handling and race-conditions).
That said, you still can make the code flow inside firstFunction
to be asynchronous and use either callback or promise to notify the caller. You'd have to give up upon for
loop and simulate it with if
instead (JSFiddle):
function firstFunction()
{
var deferred = $.Deferred();
var i = 0;
var nextStep = function() {
if (i<10) {
// Do something
printOutput("Step: " + i);
i++;
setTimeout(nextStep, 500);
}
else {
deferred.resolve(i);
}
}
nextStep();
return deferred.promise();
}
function secondFunction()
{
var promise = firstFunction();
promise.then(function(result) {
printOutput("Result: " + result);
});
}
On a side note, JavaScript 1.7 has introduced yield
keyword as a part of generators. That will allow to "punch" asynchronous holes in otherwise synchronous JavaScript code flow (more details and an example). However, the browser support for generators is currently limited to Firefox and Chrome, AFAIK.
回答3:
Use async/await :
async function firstFunction(){
for(i=0;i<x;i++){
// do something
}
return;
};
then use await in your other function to wait for it to return:
async function secondFunction(){
await firstFunction();
// now wait for firstFunction to finish...
// do something else
};
回答4:
An elegant way of waiting for one function to complete first is to use Promises with async/await function .
Quick example:
let firstFunction = new Promise(function(resolve, reject) {
let x = 10
let y = 0
setTimeout(function(){
for(i=0;i<x;i++){
y++
}
console.log('loop completed')
resolve(y)
}, 2000)
})
Firstly, create a Promise. The function above will be completed after 2s. I used setTimeout
in order to demonstrate the situation where the instructions would take some time to execute (based on your example).
async function secondFunction(){
let result = await firstFunction
console.log(result)
console.log('next step')
};
secondFunction()
For the second function, you can use async/await function where you will await for the first function to complete before proceeding with the instructions.
Note: You could simply resolve the Promise without any value like so resolve()
. In my example, I resolved the Promise with the value of y
that I can then use in the second function.
Hope it helps for people who still visit this thread.
回答5:
The only issue with promises is that IE doesn't support them. Edge does, but there's plenty of IE 10 and 11 out there: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise (compatibility at the bottom)
So, JavaScript is single-threaded. If you're not making an asynchronous call, it will behave predictably. The main JavaScript thread will execute one function completely before executing the next one, in the order they appear in the code. Guaranteeing order for synchronous functions is trivial - each function will execute completely in the order it was called.
Think of the synchronous function as an atomic unit of work. The main JavaScript thread will execute it fully, in the order the statements appear in the code.
But, throw in the asynchronous call, as in the following situation:
showLoadingDiv(); // function 1
makeAjaxCall(); // function 2 - contains async ajax call
hideLoadingDiv(); // function 3
This doesn't do what you want. It instantaneously executes function 1, function 2, and function 3. Loading div flashes and it's gone, while the ajax call is not nearly complete, even though makeAjaxCall()
has returned. THE COMPLICATION is that makeAjaxCall()
has broken its work up into chunks which are advanced little by little by each spin of the main JavaScript thread - it's behaving asychronously. But that same main thread, during one spin/run, executed the synchronous portions quickly and predictably.
So, the way I handled it: Like I said the function is the atomic unit of work. I combined the code of function 1 and 2 - I put the code of function 1 in function 2, before the asynch call. I got rid of function 1. Everything up to and including the asynchronous call executes predictably, in order.
THEN, when the asynchronous call completes, after several spins of the main JavaScript thread, have it call function 3. This guarantees the order. For example, with ajax, the onreadystatechange event handler is called multiple times. When it reports it's completed, then call the final function you want.
I agree it's messier. I like having code be symmetric, I like having functions do one thing (or close to it), and I don't like having the ajax call in any way be responsible for the display (creating a dependency on the caller). BUT, with an asynchronous call embedded in a synchronous function, compromises have to be made in order to guarantee order of execution. And I have to code for IE 10 so no promises.
Summary: For synchronous calls, guaranteeing order is trivial. Each function executes fully in the order it was called. For a function with an asynchronous call, the only way to guarantee order is to monitor when the async call completes, and call the third function when that state is detected.
For a discussion of JavaScript threads, see: https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 and https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
Also, another similar, highly rated question on this subject: How should I call 3 functions in order to execute them one after the other?
回答6:
I'm really confused about the process...considering that I have a internal function, how can I return to the outer function the result (true/false) !?!
function confirmar2(texto)
{
var modalConfirm = function(callback){
$("#modalConfirmacaoTexto").html(texto);
$("#modalConfirmacaoBotaoSim").unbind("click");
$("#modalConfirmacaoBotaoSim").on("click", function(){callback(true);$("#modalConfirmacao").modal('hide');return;});
$("#modalConfirmacaoBotaoNao").unbind("click");
$("#modalConfirmacaoBotaoNao").on("click", function(){callback(false);$("#modalConfirmacao").modal('hide');return;});
$('#modalConfirmacao').modal('show');
};
modalConfirm(function(confirm){
if(confirm)
{
return true;
}
else
{
return false;
}
});
}
回答7:
This what I came up with, since I need to run several operations in a chain.
<button onclick="tprom('Hello Niclas')">test promise</button>
<script>
function tprom(mess) {
console.clear();
var promise = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(mess);
}, 2000);
});
var promise2 = new Promise(async function (resolve, reject) {
await promise;
setTimeout(function () {
resolve(mess + ' ' + mess);
}, 2000);
});
var promise3 = new Promise(async function (resolve, reject) {
await promise2;
setTimeout(function () {
resolve(mess + ' ' + mess+ ' ' + mess);
}, 2000);
});
promise.then(function (data) {
console.log(data);
});
promise2.then(function (data) {
console.log(data);
});
promise3.then(function (data) {
console.log(data);
});
}
</script>