我有一个执行外部服务请求的小应用程序。
final app = Alfred();
app.post('/ctr', (req, res) async { // complex tender request
var data = await req.body;
await complexTendexAPIRequest(data as Map<String, dynamic>);
print('Hello World');
await res.json({'data': 'ok'});
});
处理程序代码:
complexTendexAPIRequest(Map<String, dynamic> data) async {
print('Request: $data');
try {
final response = await http.post(
Uri.parse(COMPLEX_URL),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'},
body: json.encode(data)
);
if(response.statusCode == 200) {
var res = json.decode(response.body);
int latestId = res['id'];
String url = 'https://api.ru/v2/complex/status?id=$latestId';
stdout.write('Waiting for "complete" status from API: ');
Timer.periodic(Duration(seconds: 1), (timer) async {
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
var data = json.decode(response.body);
if(data['status'] == 'completed') {
timer.cancel();
stdout.write('[DONE]');
stdout.write('nFetching result: ');
String url = "https://api.ru/v2/complex/results?id=$latestId";
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
stdout.write('[DONE]');
var data = prettyJson(json.decode(response.body));
await File('result.json').writeAsString(data.toString());
print("nCreating dump of result: [DONE]");
}
});
}
else {
print('[ERROR] Wrong status code for complex request. StatusCode: ${response.statusCode}');
}
}
on SocketException catch(e) {
print('No Internet connection: $e');
} on TimeoutException catch(e) {
print('TenderAPI Timeout: $e');
} on Exception catch(e) {
print('Some unknown Exception: $e');
}
}
但是输出很奇怪,看起来不等待complexTendexAPIRequest
完成,继续前进:
Waiting for "complete" status from API: Hello World
[DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
但应该是:
Waiting for "complete" status from API: [DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
Hello World
我想原因可能是在Timer.periodic
,但如何修复它以获得预期的顺序和执行:
print('Hello World');
await res.json({'data': 'ok'});
仅在complexTendexAPIRequest
完成后。
upd:我将代码重写为while
循环:https://gist.github.com/bubnenkoff/fd6b4f0d7aeae7007680e7902fbdc1e9看起来还可以。
Alfred https://github.com/rknell/alfred
问题在于Timer.periodic
,正如其他人指出的那样。
你:
Timer.periodic(Duration(seconds: 1), (timer) async {
// do something ...
});
设置一个计时器,然后立即继续执行。计时器每秒钟触发一次,调用async回调(它返回一个没有人等待的未来),并做一些可能需要超过一秒的事情。
你可以把它转换成一个普通的循环,基本上:
while (true) {
// do something ...
if (data['status'] == 'completed') {
// ...
break;
} else {
// You can choose your own delay here, doesn't have
// to be the same one every time.
await Future.delayed(const Duration(seconds: 1));
}
}
如果你仍然希望它是定时器驱动的,有固定的刻度,考虑重写为:
await for (var _ in Stream.periodic(const Duration(seconds: 1))) {
// do something ...
// Change `timer.cancel();` to a `break;` at the end of the block.
}
这里你创建了一个每秒触发一个事件的流。然后使用await for
循环等待每个蒸汽事件。如果你在循环内做的事情是异步的(做一个await
),那么你甚至可以确保下一个流事件被延迟,直到循环体完成,所以你不会有两个取出同时运行。和如果代码抛出,错误将被周围的try
/catch
捕获,我认为这是有意的,而不是在没有人听的Future
中结束未捕获的错误。
如果你想保留Timer.periodic
代码,你可以,但是你需要做一些额外的事情来同步它周围的async
/await
代码(它只真正理解期货和流,而不是计时器)。例如:
var timerDone = Completer();
Timer.periodic(const Duration(seconds: 1), (timer) async {
try {
// do something ...
// Add `timerDone.complete();` next to `timer.cancel()`.
} catch (e, s) {
timer.cancel();
timerDone.completeError(e, s);
}
});
await timerDone.future;
这段代码使用Completer
来完成一个未来,并有效地弥合了计时器和可以等待的未来之间的差距(文档中列出的Completer
的用途之一)。如果一个步骤花费的时间超过1秒,则定时器可能会并发运行。
同样,你可以使用retry
包,如果它是相同的东西,你想重试,直到它工作。