等待一项功能完成再继续的正确方法?


187

我有两个JS函数。一个叫另一个。在调用函数中,我想调用另一个函数,等待该函数完成,然后继续。因此,例如/伪代码:

function firstFunction(){
    for(i=0;i<x;i++){
        // do something
    }
};

function secondFunction(){
    firstFunction()
    // now wait for firstFunction to finish...
    // do something else
};

我想出了这个解决方案,但不知道这是否是明智的解决方案。

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

那是合法的吗?有没有更优雅的处理方式?也许用jQuery?


11
究竟firstFunction是什么使它异步?无论哪种方式-检查有关的承诺
zerkms

第一项功能是每10秒快速更新一个计分时钟。想到这个...我想我们可以预先计算,然后通过setTimeout手动暂停第二个函数调用。话虽如此,我仍然可以看到在其他地方具有暂停能力的渴望。
DA。

您不需要暂停
-Google

@zerkms的承诺看起来很有趣!仍在调查浏览器支持...
DA。

1
我也有同样的问题。
Vappor Washmade

Answers:


140

处理此类异步工作的一种方法是使用回调函数,例如:

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

按照@Janaka Pushpakumara的建议,您现在可以使用箭头功能实现相同的目的。例如:

firstFunction(() => console.log('huzzah, I\'m done!'))


更新:我很早以前就回答了这个问题,我真的很想对其进行更新。虽然回调绝对好,但以我的经验,它们往往会导致难以阅读和维护的代码。在某些情况下,我仍然使用它们,例如将进行中的事件等作为参数传递。此更新只是为了强调替代方案。

同样,原始问题也没有具体提及异步,因此如果有人感到困惑,如果您的函数是同步的,则在调用时它将阻塞。例如:

doSomething()
// the function below will wait until doSomething completes if it is synchronous
doSomethingElse()

如果虽然隐含了该函数是异步的,那么我今天处理所有异步工作的方式是使用async / await。例如:

const secondFunction = async () => {
  const result = await firstFunction()
  // do something else here after firstFunction completes
}

IMO,异步(async / await)使您的代码比直接使用promise(大多数情况下)更具可读性。如果您需要处理捕获错误,则将其与try / catch一起使用。在此处了解更多信息:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function


9
@zerkms-想详细说明吗?
马特路

7
回调不方便处理异步,承诺更加轻松和灵活
zerkms 2014年

1
虽然您说对了,我不应该使用“最佳”(更新)一词,但回调与promise的方便性取决于问题的复杂性。
马特路

3
它变得不合时宜了,但是我个人最近没有理由偏爱回调:-)我不记得我过去两年写的任何代码,它们都提供了对Promise的回调。
zerkms 2014年

3
如果需要,可以在es6中使用箭头功能secondFunction(){firstFunction((response)=> {console.log(response);}); }
Janaka Pushpakumara

54

使用async / await:

async function firstFunction(){
  for(i=0;i<x;i++){
    // do something
  }
  return;
};

然后在其他函数中使用await等待它返回:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};

9
对于那些坚持支持较旧浏览器的人,IE不支持异步/等待
凯尔

可以在两个功能之外使用该功能$(function() { await firstFunction(); secondFunction(); });吗?
Lewistrick

1
@Lewistrick请记住await,只能在方法内部使用async。因此,如果您使用父函数async,则可以async methods在需要的情况下调用任意数量的内部函数await
Fawaz

是否有可能await一个setTimeout
莎彦

1
@Shayan是的,将setTimeout包装在另一个函数中,该函数可以在超时后解析promise。
Fawaz

51

似乎您在这里缺少要点:JavaScript是一个单线程执行环境。让我们再次查看您的代码,注意我已经添加了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
        };
    }
};

您不必等待isPaused。当您看到“此处”警报时,isPausedfalse已经存在,并将firstFunction返回。那是因为您不能从for循环(// do something)内部“屈服” ,循环可能不会中断,并且必须首先完全完成(更多详细信息:Javascript线程处理和race-conditions)。

也就是说,您仍然可以使代码流在内部firstFunction成为异步流,并使用回调或Promise通知调用者。您必须放弃for循环并if改为使用(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);
    });
}

附带说明,JavaScript 1.7引入了yield关键字作为generators的一部分。这将允许在其他同步JavaScript代码流中“打孔”异步漏洞(更多详细信息和示例)。但是,浏览器对生成器的支持目前仅限于Firefox和Chrome,AFAIK。


3
你救了我。$ .Deferred()是我一直追求的目标。谢谢
Temitayo

@noseratio我尝试了这个。但是,根本不会调用waitForIt方法。我究竟做错了什么?
vigamage

@vigamage,能否提供您尝试过的jsfiddle或codepen的链接?
noseratio

1
您好,谢谢您的关注。我知道了 谢谢..!
vigamage

18

等待一个功能首先完成的一种优雅方法是将Promisesasync / await功能一起使用。


  1. 首先,创建一个Promise。我创建的功能将在2秒后完成。我 setTimeout为了演示这种情况而需要执行一些指令。
  2. 对于第二个功能,您可以使用 async / await 功能,await在继续执行说明之前,您将使第一个功能完成。

例:

    //1. Create a new function that returns a promise
    function firstFunction() {
      return new Promise((resolve, reject) => {
          let y = 0
          setTimeout(() => {
            for(i=0; i<10; i++){
               y++
            }
             console.log('loop completed')  
             resolve(y)
          }, 2000)
      })
    }
    
    //2. Create an async function
    async function secondFunction() {
        console.log('before promise call')
        //3. Await for the first function to complete
        let result = await firstFunction()
        console.log('promise resolved: ' + result)
        console.log('next step')
    }; 

    secondFunction()


注意:

你可以简单地resolvePromise没有像这样的任何值resolve()。在我的例子,我resolvedPromise用的价值y,我可以然后在第二个函数中使用。


谢谢,它有效!
modInfo

是的,这将始终有效。如果有人参与这种情况,那么JavaScript Promise将为他们解决问题。
Puttamarigowda MS

5

承诺的唯一问题是IE不支持它们。Edge确实可以,但是有很多IE 10和11:https//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise(底部兼容)

因此,JavaScript是单线程的。如果您不进行异步调用,它将可以预期地运行。JavaScript主线程将按执行顺序在代码中出现的顺序完全执行一个功能。保证同步功能的顺序很简单-每个功能将完全按照其调用顺序执行。

将同步功能视为工作的原子单元。JavaScript主线程将按照语句在代码中出现的顺序完全执行它。

但是,引发异步调用,如下所示:

showLoadingDiv(); // function 1

makeAjaxCall(); // function 2 - contains async ajax call

hideLoadingDiv(); // function 3

这并不能满足您的要求。它立即执行功能1,功能2和功能3。尽管ajax调用makeAjaxCall()返回了,但它几乎没有完成,而加载div闪烁并消失了。复杂之处在于,makeAjaxCall()将其工作分解为多个块,每个主要JavaScript线程的每次旋转都会逐步推进这些块 -它的行为是不同步的。但是,同一主线程在一次旋转/运行期间可以快速且可预测地执行同步部分。

因此,我的处理方式:就像我说的那样,功能是工作的原子单位。我将函数1和2的代码合并在一起-在异步调用之前,将函数1的代码放入函数2中。我摆脱了功能1。直到异步调用为止的所有工作都可以按顺序执行。

然后,在异步调用完成后,在主JavaScript线程旋转几次之后,让其调用函数3。这样可以保证顺序。例如,对于ajax,onreadystatechange事件处理程序被多次调用。报告完成时,请调用所需的最终函数。

我同意这比较麻烦。我喜欢让代码对称,我喜欢让函数做一件事情(或接近它),而且我不喜欢让ajax调用以任何方式负责显示(对调用者产生依赖性)。但是,在异步函数中嵌入了异步调用的情况下,必须做出妥协才能保证执行顺序。而且我必须为IE 10编写代码,所以没有任何希望。

摘要:对于同步呼叫,保证顺序很简单。每个函数均按其调用顺序完全执行。对于具有异步调用的函数,保证顺序的唯一方法是监视异步调用何时完成,并在检测到该状态时调用第三个函数。

有关JavaScript线程的讨论,请参见:https : //medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/事件循环

另外,与此主题相关的另一个类似的,评分很高的问题:我应该如何调用3个函数才能一个接一个地执行它们?


8
对于那些拒绝投票的人,我很想知道这个答案出了什么问题。
kermit

可能发生了太多的格式化?内容方面对我来说似乎很好
tschoppi

0

这是我想出的,因为我需要在链中运行多个操作。

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

请至少添加一个高级注释,以描述给定的代码段。这是为了说明您的代码如何解决该问题。
Cold Cerberus

问题是要在第一个函数之后运行第二个函数。我展示了如何在第一个函数之后运行第二个函数,以及如何在第二个函数之后运行第三个函数。我编写了三个函数,以使其易于理解所需的代码。
Niclas Arnberger

0

您的主要乐趣将调用firstFun,然后在完成后调用您的下一个乐趣。

async firstFunction() {
            const promise = new Promise((resolve, reject) => {
                for (let i = 0; i < 5; i++) {
                    // do something
                    console.log(i);
                    if (i == 4) {
                        resolve(i);
                    }
                }
            });
            const result = await promise;
        }

        second() {
            this.firstFunction().then( res => {
                // third function call do something
                console.log('Gajender here');
            });
        }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.