就像How to avoid "if" chains?一样,这个问题是关于应该如何保持异步程序的清洁。
异步编程是一种完全不同于程序编程的范例,当你深入了解它时它真的很有趣。但是,当您进行多次异步调用时,它可能会变得混乱。
考虑这个例子(这是我遇到的现实场景的抽象):
a.txt
a.txt
的内容提交给服务器X
进行处理并检索输出。b.txt
。b.txt
的内容和服务器X
的响应(步骤2)发送到服务器Y
,然后获取输出。Y
的响应(步骤4)保存到c.txt
。使用异步调用,我的JavaScript看起来与此类似(函数名称组成。所有函数都是带回调的异步调用。省略了错误参数以提高清晰度。带有错误参数和错误处理的实际代码比这更麻烦):
readFile('a.txt', function (file_a_data) {
requestToServer('http://xserver.com/', file_a_data, function (server_x_response) {
readFile('b.txt', function (file_b_data) {
var request_params = server_x_response + file_b_data;
requestToServer('http://yserver.com/', request_params, function (server_y_reponse) {
writeFile('c.txt', server_y_response);
});
});
});
});
正如您所看到的,已经有四个级别的缩进,并且正在构建一个代码箭头。 我们如何避免这种回调嵌套,并编写一些干净的代码?
这样做的一种方法是尽可能编写函数的同步,无回调版本。返回一个值并抛出错误异常。
try {
var file_content = readFile('a.txt');
// do stuff with file_content
} catch (ex) {
// an error occured
}
但是这个问题很少:
将所有回调命名为函数,并仅指定回调的函数名称:
function processFileA(data) {
requestToServer('http://xserver.com/', file_a_data, processRequestX);
}
function processRequestX(response) {
readFile('b.txt', function (file_b_data) {
var request_params = server_x_response + file_b_data;
requestToServer('http://yserver.com/', request_params, processRequestY);
});
}
function processRequestY(response) {
writeFile('c.txt', server_y_response);
}
readFile('a.txt', processFileA);
这类似于How to avoid hard-coded, chained asynchronous functions in Javascript/jQuery?这个问题看起来像我的问题的反转。这个问题的编码风格是我用来阻止链条的一种补救措施,但看起来并不是很好。我认为它看起来像薄包装和意大利面条:
how to avoid callback chains?:一个类似的问题(标题中),但这并不是真正的异步回调。这是关于将函数作为参数传递。
答案 0 :(得分:5)
使用Promises!这正是他们的目的:
// assume 'readFile' and 'requestToServer' return a promise for their result
readFile('a.txt').then(function(file_a_data) {
return requestToServer('http://x.example.com/', file_a_data);
}).then(function(server_x_response) {
return readFile('b.txt').then(function(file_b_data) {
return server_x_response + file_b_data;
});
}).then(function(request_params) {
return requestToServer('http://y.example.com/', request_params);
}).then(function(server_y_reponse) {
writeFile('c.txt', server_y_response);
});
(是的,它保持干净,有没有附加,杂乱的错误处理代码)
如果您利用一些函数式编程并使用curried函数,或者至少部分应用它们,它可能会变得更加干净。即使并行阅读a
和b
也很容易实现:
Promise.all([
readFile('a.txt').then(requestToServer.partial('http://x.example.com/')),
readFile('b.txt')
])
.spread(function(server_x_response, file_b_data) {
return server_x_response + file_b_data;
})
.then(requestToServer.partial('http://y.example.com/'))
.then(writeFile.partial('c.txt'));