异步处理程序执行顺序错误



我有一个执行外部服务请求的小应用程序。

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包,如果它是相同的东西,你想重试,直到它工作。

相关内容

  • 没有找到相关文章

最新更新