How do I avoid callback chaining in asynchronous programming?

How can I avoid "if" chains? , this question is about what to do to keep asynchronous programs clean.

Asynchronous programming is a completely different paradigm of procedural programming and it's really fun when you wrap your arms around it. However, it can get messy if you have multiple asynchronous calls.

Consider this example (this is an abstraction of a real world scenario I ran into):

  • Read file a.txt

  • Submit content a.txt

    to the server X

    for processing and receiving output.
  • Read the file b.txt

    .
  • Send content b.txt

    and response from server X

    (step 2) to server Y

    and receive output.
  • Save response from server Y

    (step 4) to c.txt

    .

With asynchronous calls, my JavaScript is like this (function names are composed). All functions are asynchronous calls with callbacks. Error parameters have been omitted to improve clarity. The real code with error parameters and error handling is much dirtier than this):

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

      

As you can see, there are already four levels of indentation and the code arrow is incrementing. How can we avoid this nesting of callbacks and write clean code?

What I have tried so far:

  • One way to do this is to write synchronous, non-reverse versions of the functions whenever possible. A value is returned and an error exception is thrown.

    try {
        var file_content = readFile('a.txt');
        // do stuff with file_content
    } catch (ex) {
        // an error occured
    }
    
          

    But there are several problems with this:

    • For heavy I / O programs such as performance, the performance is very high.
    • Elementary API functions (such as XmlHttpReqeust or the Node.js File System API already have callbacks. These are asynchronous parts and there is little we can do other than write thin, synchronous wrappers.

  • Executing all the callbacks of named functions and just specifying the function name for the callback:

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

    It is similar to How to avoid hardcoded asynchronous functions in Javascript / jQuery? This question looks like the inverse of my problem. The coding style of this question was one of the tools I used to stop the chains, but it doesn't look good. I think it looks like thin wrappers and spaghetti:

  • how to avoid callback chains? : a similar question (in the title), but this does not apply to asynchronous callbacks. It's about passing functions as parameters.

+3


source to share


1 answer


Use Promises ! This is exactly what was done for:

// 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);
});

      

(Yes, it stays as clean as this one, no extra, messy error handling code)



It can get even cleaner if you use a little functional programming and use currency functions, or at least partially use them. Even reading a

and b

parallel would be trivially achievable:

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

      

+5


source







All Articles