290

我有两个 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?

4

10 回答 10

200

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

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 。

于 2014-02-03T01:24:05.880 回答
89

使用异步/等待:

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
};
于 2018-08-17T11:38:59.243 回答
58

看来您在这里遗漏了重要的一点: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。当您看到“此处”警报时,已经是并且isPaused将会返回。那是因为你不能从循环()内部“屈服”,循环可能不会被中断,并且必须首先完全完成(更多细节:Javascript线程处理和竞争条件)。falsefirstFunctionfor// do something

也就是说,您仍然可以使内部的代码流firstFunction异步,并使用回调或承诺来通知调用者。您必须放弃for循环并使用ifJSFiddle)来模拟它:

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。

于 2014-02-03T09:59:55.273 回答
57

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


  1. 首先,创建一个Promise。我创建的功能将在 2s 后完成。我用来 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
        const result = await firstFunction()
        console.log('Promise resolved: ' + result)
        console.log('Next step.')
    }; 

    secondFunction()


笔记:

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

于 2019-04-16T00:20:39.567 回答
21

我想知道为什么没有人提到这个简单的模式?:

(function(next) {
  //do something
  next()
}(function() {
  //do some more
}))

仅仅为了盲目等待而使用超时是不好的做法;并且涉及承诺只会增加代码的复杂性。在 OP 的情况下:

(function(next) {
  for(i=0;i<x;i++){
    // do something
    if (i==x-1) next()
  }
}(function() {
  // now wait for firstFunction to finish...
  // do something else
}))

一个小演示-> http://jsfiddle.net/5jdeb93r/

于 2020-10-11T16:26:47.340 回答
6

promise 的唯一问题是 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。加载 div 闪烁并且它消失了,而 ajax 调用尚未完成,即使makeAjaxCall()已经返回。复杂之处在于,它makeAjaxCall()已将其工作分解为多个块,这些块通过主 JavaScript 线程的每次旋转一点一点地推进——它的行为是异步的。但是同一个主线程,在一次旋转/运行期间,快速且可预测地执行同步部分。

所以,我处理它的方式:就像我说的那样,函数是工作的原子单元。我结合了函数 1 和 2 的代码 - 我将函数 1 的代码放在函数 2 中,在异步调用之前。我摆脱了功能 1。包括异步调用在内的所有内容都按顺序按可预测的方式执行。

然后,当异步调用完成时,在主 JavaScript 线程几次旋转之后,让它调用函数 3。这保证了 order。例如,对于 ajax,onreadystatechange 事件处理程序会被多次调用。当它报告它已完成时,然后调用您想要的最终函数。

我同意它更混乱。我喜欢让代码对称,我喜欢让函数做一件事(或接近它),而且我不喜欢让 ajax 调用以任何方式负责显示(创建对调用者的依赖)。但是,对于嵌入在同步函数中的异步调用,必须做出妥协以保证执行顺序。而且我必须为 IE 10 编写代码,所以没有任何承诺。

摘要:对于同步调用,保证顺序是微不足道的。每个函数都按照它被调用的顺序完全执行。对于具有异步调用的函数,保证顺序的唯一方法是监视异步调用何时完成,并在检测到该状态时调用第三个函数。

有关 JavaScript 线程的讨论,请参阅:https ://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23和https://developer.mozilla.org/en-US/docs/Web/JavaScript/事件循环

此外,关于这个主题的另一个类似的、评价很高的问题:我应该如何调用 3 个函数以便一个接一个地执行它们?

于 2018-07-07T04:20:43.433 回答
4

可以使用承诺

Promise 是一个 JavaScript 结构,代表一个未来的未知值。它可能是 API 调用的结果,也可能是网络请求失败的错误对象。你一定会得到一些东西。

const promise = new Promise((resolve, reject) => {
    // Make a network request
   if (yourcondition=="value") {
      
   } else {
      reject(error);
   }
})

promise.then(res => {
    //if not rejected, code

}).catch(err => {
    //return false; 
})

一个承诺可以有

已完成 - 操作已成功完成

拒绝 - 操作失败

待定 - 两个操作都未完成

已解决 - 已完成或已拒绝

于 2021-03-26T09:15:54.347 回答
1

您的主要乐趣将调用 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');
            });
        }
于 2020-05-14T10:58:42.383 回答
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>
于 2019-10-24T02:52:58.603 回答
-1

尝试这个

function firstFunction(){
    
    // do something  
    X=true;
    return X;
}

function secondFunction(){
    X=false;
    X=firstFunction();
    setTimeout( function() {
        if(X==true){
            // do something else
        }else{
            // do something else
        }
        alert(X); // To check X 
       
    }, 100); // Can increase time delay 200,300, ...
}

将时间100 增加到 200、300、... 基于 firstFunction 完成所需的时间

于 2021-08-28T04:58:57.297 回答