2016-06-18 5 views
1

У меня есть эта функция:Как сделать цикл «для» с асинхронным состоянием в Javascript?

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(), чтобы закончить выполнение перед продолжением цикла?

Также следует отметить, что это не в исполнении браузера, поэтому мне все равно, что висит.

+0

Does 'self.api.connect' вернуть какой-то отложенного объекта или обещания? Вы должны дождаться разрешения объекта перед тем, как перейти к следующему элементу. – Terry

+0

@nnnnnn, я хочу, чтобы он звонил 10 раз и ждал 1 секунду между вызовами. Мне все равно, как долго проходит звонок. Предположим, что за одну минуту каждый звонок. Таким образом, он мог подождать 10 минут и 10 секунд, и со мной все будет в порядке. – Prostak

+0

@Terry, мой плохой, на самом деле getMemberInfo возвращает какой-то объект. – Prostak

ответ

1

У меня сегодня нет моего рабочего ноутбука, потому что это воскресенье, я кодирую это на возвышенном. Извините, если синтаксис немного выключен.

Чтобы решить вашу проблему, я рекомендую изменить isMemberFree(), чтобы выполнить функцию обратного вызова. Это связано с тем, что isMemberFree является асинхронным, и вам понадобится способ сообщить результат после его выполнения.

Затем смените тестовую функцию на использование API setTimeout, чтобы подождать секунду. Оберните вызов функции для 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() не вернется, либо не будет превышено 10 секунд ожидания.

+0

Несвободный случай должен, вероятно, называть 'done (null, false)'. – nnnnnn

+0

ok modifying that –

+0

@SamuelToh, спасибо за помощь, но он дает 'SyntaxError: Illegal break statement'. Чтобы упростить это, вы могли бы включить эти две функции в одну. Я имею в виду, что одна из них будет содержать обе функции. – Prostak

2

При работе с асинхронным кодом вам необходимо использовать обратные вызовы. То есть, если вы хотите сделать a() и b() на заказ, но a() делает что-то асинхронно, тогда вам нужно позвонить b() из a() после того, как a() имеет результат. Так нет:

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() s, чтобы сделать код короче.)

Обратите также внимание на то, что вы можете структурировать вышеуказанное, чтобы использовать обещания, но конечный результат будет таким же.

+0

Спасибо за подробное объяснение и вашу помощь! Ценить это. Тем не менее, я отдам его Самуэлю Тоху, поскольку его ответ был ранее, и я успешно его реализовал. – Prostak

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

здание на 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'); 
     } 
    } 
}; 

Сейчас вам нужно использовать transpiler как Babel или Traceur, прежде чем можно реально использовать асинхронный/Await, хотя. Это only supported в Microsoft Edge 14 прямо сейчас.

И большой акцент, что то, что возвращается с 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();