如何避免异步编程中的回调链?

时间:2014-10-15 12:37:35

标签: javascript asynchronous callback

就像How to avoid "if" chains?一样,这个问题是关于应该如何保持异步程序的清洁。

异步编程是一种完全不同于程序编程的范例,当你深入了解它时它真的很有趣。但是,当您进行多次异步调用时,它可能会变得混乱。

考虑这个例子(这是我遇到的现实场景的抽象):

  1. 读取文件a.txt
  2. a.txt的内容提交给服务器X进行处理并检索输出。
  3. 读取文件b.txt
  4. b.txt的内容和服务器X的响应(步骤2)发送到服务器Y,然后获取输出。
  5. 将来自服务器Y的响应(步骤4)保存到c.txt
  6. 使用异步调用,我的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
      }
      

      但是这个问题很少:

      • 对于像这样的I / O重型程序,性能影响非常高。
      • 基本API函数(例如XmlHttpReqeustNode.js File System API已经有回调。它们异步部分,除了写瘦之外我们几乎无能为力同步包装。

    • 将所有回调命名为函数,并仅指定回调的函数名称:

      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?:一个类似的问题(标题中),但这并不是真正的异步回调。这是关于将函数作为参数传递。

1 个答案:

答案 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函数,或者至少部分应用它们,它可能会变得更加干净。即使并行阅读ab也很容易实现:

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'));