Is there a JavaScript preprocessor that makes call

2020-07-22 16:54发布

问题:

JavaScript toolkits like jQuery are all about callback functions, and those callbacks are frequently defined anonymously. Example: Some webpage shows a list of messages in a table. To update this table, it might first ask the server for a list of all current messages (as IDs), then retrieve the content for the yet-unknown message IDs:

function fnUpdateMessages() {
   $.ajax({
      type: 'POST',
      data: { action: 'get_message_ids' },
      success: function(sData) {
         var aMessageIds = sData.split(/,/);
         var aUnknownIds = fnWhichIdsAreNotInTable(aMessageIds);
         $.ajax({
            type: 'POST',
            data: {
               action: 'get_message_contents',
               ids: aUnknownIds.join(',')
            },
            success: function(oData) {
               for (var id in oData.messages) {
                  fnInsertMessage(oData.messages[id]);
               }
            }
         );
      }
   );
}

You see where I'm going? This code is ugly, since indentation is at level 6 after only 2 subsequent AJAX calls. I can of course split the anonymous functions into separate functions at the file scope, but that usually pollutes the namespace (unless one clutters stuff further by wrapping this in another anonymous function call) and it breaks the strong bond between these functions: The callbacks should really not be used by themselves; they're just like the second and third part of the original fnUpdateMessages function.

What I'd much rather want is something like this:

function fnUpdateMessages() {
   $.ajax({
      type: 'POST',
      data: { action: 'get_message_ids' },
      success: continue(sData)
   });

   var aMessageIds = sData.split(/,/);
   var aUnknownIds = fnWhichIdsAreNotInTable(aMessageIds);
   $.ajax({
      type: 'POST',
      data: {
         action: 'get_message_contents',
         ids: aUnknownIds.join(',')
      },
      success: continue(oData)
   );

   for (var id in oData.messages) {
      fnInsertMessage(oData.messages[id]);
   }
}

This snippet introduces new hypothetical syntax continue(var1, var2, [...]) which defines an anonymous callback function whose body is everything that follows in the enclosing function scope. This makes those callback functions appear like synchronous code. That would have to be preprocessed, obviously, since it's not standard JS.

Before I even consider writing such a preprocessor, I would like to know if something like this already exists?

P.S. If you like this idea, please steal it. I can't quite afford yet another project at the moment. A link to your repository in a comment would be great, should you get to some working code.

回答1:

There are only two solutions :

The first is really bad : you have to make the first ajax request synchronous but your script will block until result is available. This is really a bad solution, you should not make any ajax requests synchronous.

The second one use the jQuery.pipe function on deferred object return by $.ajax (you have to use jquery > 1.5). You can chain callbacks using pipe like this (I use internal function to make it more readable) :

[EDIT] : since jquery 1.8, you should use deferred.then instead of deferred.pipe :

    function fnUpdateMessages() {
        var getMessages = function() {
            return $.ajax({
                type: 'POST',
                data: { action: 'get_message_ids' },
            });
        };

        var getContents = function(aUnknownIds) {
            return $.ajax({
                type: 'POST',
                data: {
                    action: 'get_message_contents',
                    ids: aUnknownIds.join(',')
                },
            });
        };

        var insertMessages = function(oData) {
            for (var id in oData.messages) {
                fnInsertMessage(oData.messages[id]);
            }
        };

        getMessages()
            .then(getContents)
            .done(insertMessages);
     }


回答2:

You can use jQuery's deferreds to chain callbacks instead of including them in the options.

function fnUpdateMessages() {
   $.ajax({
      type: 'POST',
      data: { action: 'get_message_ids' }
   ).done(function(sData) {
      var aMessageIds = sData.split(/,/);
      var aUnknownIds = fnWhichIdsAreNotInTable(aMessageIds);
      $.ajax({
         type: 'POST',
         data: {
            action: 'get_message_contents',
            ids: aUnknownIds.join(',')
         }
      }).done(function(oData) {
         for (var id in oData.messages) {
            fnInsertMessage(oData.messages[id]);
         }
      });
   });
}

It's not perfect, but it'll save you a couple levels of indentation per request.

See the documentation for $.ajax for more info.



回答3:

Yes there is. It's called jwacs - JavaScript With Advanced Continuation Support. To put it simply you make use of continuations to suspend the execution of a program. You may then resume execution of the program by calling the continuation. The continuation always preserves the state of the program at the time it was created.

This is a little like trampolining in JavaScript, but trampolining depends on generators which are only supported by Mozilla products - Firefox and Rhino. If you're interested in trampolining I wrote a library to make writing asynchronous bearable. It's called Fiber and it's a little like cooperative Java threads.

On the other hand jwacs compiles down to ordinary JavaScript. Hence it may be used on any platform. Not just Firefox and Rhino. If you want to understand what continuations are then I suggest you read the following StackOverflow question and answer:

The Question: What's the difference between a continuation and a callback?

The Answer: https://stackoverflow.com/a/14022348/783743