如何在 Javascript 中使用异步条件执行 "for" 循环?
How to do a "for" loop with asynchronous condition in 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()
完成执行后再继续循环?
还要注意,这不是在浏览器执行中,所以我不关心任何挂起。
我今天没有工作笔记本电脑,因为是星期天,我正在用 sublime 编写代码。如果语法有点不对,请见谅。
为了解决您的问题,我建议更改 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() returns true 或超过 10 秒等待。
在处理异步代码时,需要使用回调。也就是说,如果您想按顺序执行 a()
和 b()
但 a()
异步执行某些操作,那么您需要从 a()
中调用 b()
一次 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()
以使代码更短。)
另请注意,您可以构建以上内容以使用承诺,但最终结果将是相同的。
你可以return一个承诺
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);
});
基于 , if you do it that way you can pretty easily use a for
loop with it, using the (most likely) upcoming 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');
}
}
};
现在您需要在 Microsoft Edge 14 中使用像 Babel or Traceur before you can really use async/await, though. It's only supported 这样的转译器。
并且要强调的是,从 test()
中 return 编辑的内容并不是您直接从其中 return 得到的任何内容。如果我这样做:
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();
我有这个功能:
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()
完成执行后再继续循环?
还要注意,这不是在浏览器执行中,所以我不关心任何挂起。
我今天没有工作笔记本电脑,因为是星期天,我正在用 sublime 编写代码。如果语法有点不对,请见谅。
为了解决您的问题,我建议更改 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() returns true 或超过 10 秒等待。
在处理异步代码时,需要使用回调。也就是说,如果您想按顺序执行 a()
和 b()
但 a()
异步执行某些操作,那么您需要从 a()
中调用 b()
一次 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()
以使代码更短。)
另请注意,您可以构建以上内容以使用承诺,但最终结果将是相同的。
你可以return一个承诺
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);
});
基于 for
loop with it, using the (most likely) upcoming 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');
}
}
};
现在您需要在 Microsoft Edge 14 中使用像 Babel or Traceur before you can really use async/await, though. It's only supported 这样的转译器。
并且要强调的是,从 test()
中 return 编辑的内容并不是您直接从其中 return 得到的任何内容。如果我这样做:
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();