如何做" for"在Javascript中使用异步条件循环?

时间:2016-06-18 22:11:02

标签: javascript asynchronous ecmascript-6 synchronous

我有这个功能:

waitForFreeAccnt.prototype.isMemberFree = function () {
    var self = this;

    self.api.getMemberInfo(function () {
        var accType = self.api.connect.accountType;
        console.log(accType);
        if (accType === 'FREE') {
            console.log('it is free');
            return true;
        } else {
            console.log('it is not free');
            return false;
        }
    });
};

我想等到这个账户可以免费使用10秒钟之后:

var test = function () {
    for (var start = 1; start < 10; start++) {
        var result = self.isMemberFree();
        console.log(result);
        if (result) {
            break;
        } else {
            self.api.pause(1000);
            console.log('waiting');
        }
    }
};

但它不起作用,因为self.api.getMemberInfo是异步调用。这对Javascript来说非常令人沮丧。任何其他语言都可以这么简单。在继续循环之前,如何强制for循环等待self.isMemberFree()完成执行?

另外需要注意的是,这不是浏览器的执行,所以我不在乎任何悬挂。

4 个答案:

答案 0 :(得分:2)

处理异步代码时,需要使用回调。也就是说,如果您想按顺序执行a()b()a()异步执行某些操作,那么您需要在b() a()内调用a() { {1}}有结果。所以不是:

a();  // does something asynchronously
b();  // tries to use a()'s result but it isn't available yet

......而是

a(b); // pass b to a() and a() will call it when ready

function a(callback) {
  triggerAsyncFunction(function(result) {
    if (result === something)
      callback("a just finished");
  });
}

请注意a()没有按名称引用b(),它只是调用作为参数传入的函数。

因此将其应用于您的代码,可能是这样的:

waitForFreeAccnt.prototype.isMemberFree = function (cbf) {
    var self = this;
    self.api.getMemberInfo(function () {
        cbf(self.api.connect.accountType === 'FREE');
    });
};
waitForFreeAccnt.prototype.testMemberXTimes = function(maxAttempts, callback) {
  var attempts = 0;
  var self = this;
  (function attempt() {
    self.isMemberFree(function(free) {
      if (free)
        callback(true);
      else if (++attempts < maxAttempts)
        setTimeout(attempt, 1000);
      else
        callback(false);
    });
  )();
};
this.testMemberXTimes(10, function(isFree) {
  // the next part of your code here, or called from here
  // because at this point we know we've tested up to
  // ten times and isFree tells us the result
});

请注意,我编码getMemberInfo()的方式基本上与你的相同,但是它不是返回一个布尔值,而是调用回调函数并传递你返回的相同布尔值。 (我删除了console.log()以缩短代码。)

另请注意,您可以构建上述内容以使用promises,但最终结果将是相同的。

答案 1 :(得分:2)

你可以回复承诺

waitForFreeAccnt.prototype.isMemberFree = function () {
  return new Promise((reject, resolve)=>
    // set a timeout if api call takes too long
    var timeout = setTimeout(()=> reject(Error('API timeout')), 10000);
    // make api call
    this.api.getMemberInfo(()=> {
      clearTimeout(timeout);
      resolve(this.api.connect.accountType === 'FREE');
    });
  );
};

然后像这样使用它

whatever.isMemberFree().then(isFree=> {
  if (isFree)
    console.log('it is free');
  else
    console.log('it is not free');
})
// handle timeout or other errors
.catch(err=> {
  console.log(err.message);
});

答案 2 :(得分:2)

naomik's answer上构建,如果你这样做,你可以很容易地使用for循环,使用(最有可能)即将推出的async/await功能 - 虽然它不是ES2015。

// Note "async" here! That will make "await" work. It makes the function
// return a promise, which you'll be able to either "await" or
// "test().then" later.
var test = async function () {
    for (var start = 1; start < 10; start++) {
        // Right here we're using "await" - it makes JavaScript *wait* for
        // the promise that comes from self.isMemberFree() to be finished.
        // It's really handy because you can use it in loops like "for" and
        // "while" without changing the flow of your program!
        var result = await self.isMemberFree();
        console.log(result);
        if (result) {
            break;
        } else {
            self.api.pause(1000);
            console.log('waiting');
        }
    }
};

现在,在真正使用async / await之前,您需要使用BabelTraceur之类的转发器。现在是Microsoft Edge 14中的only supported

并强调从test()返回的内容不是你直接从里面返回的内容。如果我这样做:

var test = async function() { return 15; };
var result = test();

我不会得到15 - 我会得到一个将解决为15的承诺:

result.then(function(res) {
  console.log(res); // 15
});

// or, use an async function again:
var main = async function() {
  console.log(await res); // 15
};
main();

答案 3 :(得分:1)

我今天没有工作笔记本电脑,因为它是星期天,我是在崇高的时候编写这个。如果语法稍微偏离,请道歉。

要解决您的问题,我建议更改isMemberFree()以接受回调函数。这是因为isMemberFree是异步的,你需要一种在完成工作后报告结果的方法。

然后更改测试功能以使用setTimeout API等待一秒钟。 将isMemberFree()函数调用包含在嵌套函数中并递归调用,这样就可以同步控制异步调用。

查看编码示例:

waitForFreeAccnt.prototype.isMemberFree = function (done) {
    var self = this;

    self.api.getMemberInfo(function () {
        var accType = self.api.connect.accountType;
        console.log(accType);
        if (accType === 'FREE') {
            console.log('it is free');
            return done(null, true);
        } else {
            console.log('it is not free');
            return done(null, false);
        }
    });
};


var test = function () {

    var testMembership = function(waitAttempt, isFree) {
        if (isFree) { 
            return; 
        }
        else if (waitAttempt > 10) {
            // wait exceeded, do something.
            return;
        }
        setTimeout(function() {
            self.isMemberFree(function(err, isFree) {
                testMembership(waitAttempt+=1, isFree);
            });
        }, /*total milliseconds in 1sec=*/1000);
    }

    testMembership(/*WaitAttempts=*/0, /*isFree=*/false);
};

上面的代码所做的是,大概已经对会员的帐户做了一些事情,现在调用了测试功能。所以它等待1秒,然后调用isMemberFree函数,这是递归发生的,直到isMemberFree()返回true或者超过10秒等待。