How to implement reusable callback functions

2020-07-09 09:48发布

I am fairly new to JavaScript and I am working in node which requires a good understanding of async programming and callback design. I have found that using embedded functions is very easy to do even when your callbacks are multiple levels deep. Your embedded callbacks just end up being closures.

However, when you have several layers of callbacks where many of the callbacks are similar across execute routes, you end up rewriting a lot of callback code over and over in separate callback chains. For example, if mycb1 and mycb2 definitions below are moved outside of A, they no longer have implicit access to A's variables and thus, no longer function as closures.

Example with embedded definitions where they function as closures.

mod.A=function(){
  var mycb1=function(err){
    if (!err){
      var mycb2=function(err){
        cb(err);
      };
      mod.foo2(args,mycb2);
    }
    else{cb(err);}
  };
  mod.foo1(args,mycb1);
}

mod.foo1 = function(args,cb){
  //do some work
  cb(err);
};

mod.foo2 = function(args,cb){
  //do some work
  cb(err);
} 
//execute
mod.A();

I want to do the following but be able to change the variable scope for mycb1 and mycb2 functions so they can be used as closures from where ever they are called. For example:

var mycb2=function(err){
  ...
  cb(err);
};

var mycb1=function(err){
  if (!err){        
    mod.foo2(args,mycb2);  //but have it act like a closure to mycb1
  }
  else{cb(err);}
};

mod.A=function(){
  ...
  mod.foo1(args,mycb1);  //but have it act like a closure to A
} 

mod.foo1 = function(args,cb){
  //do some work
  cb(err);
}

mod.foo2 = function(args,cb){
  //do some work
  cb(err);
}

I know that I can implement a design that either sets variable at the mod level so they are accessible to mod level functions. However, this seems to somewhat pollute the mod scope with variable that may only be used by a few of its' methods. I also know that I could pass in variables to make them accessible to the callbacks when they are executed. However, if I understand how JS and callbacks work, I would have to pass those to fooX and then have foo pass them to the callbacks. That doesn't seem like a good plan either. Can the variable scope of a function be changed so it acts like a closure from its point of execution rather than its point of definition? If not, what is the best way to modularize your callback code so it can be reused?

2条回答
Ridiculous、
2楼-- · 2020-07-09 10:16

In general, there is no getting around having to create another function in-line that has access to the closures. You can create the function in-line by having a simple anonymous function that passes some arguments to the parent callback as parameters while accepting the rest (i.e. a partial function), or by using Function.bind() to create the partial function itself.

For example, if you initially had:

function(...) {
    // closure vars x1, y1
    foo.bar( function(result) {
        // do something with x1 and y1
    });
}

You could extract that to:

var callback = function(x1, y1, result) {
    // do something with x1 and y1
};

function(...) {
    // closure vars x1, y1

    // option 1: make your own partial function
    foo.bar( function(result) { return callback(x1, y1, result); });
    // with ES6: foo.bar( (result) => callback(x1, y1, result); });

    // option 2: use Function.bind
    foo.bar( callback.bind(this, x1, y1); );
}
查看更多
三岁会撩人
3楼-- · 2020-07-09 10:32

here is a nested callback example with no closure and no nesting. it grabs the page specified, finds the third link, then shows the source of that link to the user.

in this case, when run from here in the console and fed the home page, the logout page is the third link, and it's contents are alerted.

all the callbacks are siblings, and there are no outside state variables, just pure functional async:

// look ma, no nesting!
function ajax(a, c) {
    var e = new XMLHttpRequest;
    e.onload= ajaxOnload.bind(this, c, e);
    e.open( "GET", a, !0);
    e.send();
    return e
}

function ajaxOnload(c, e) {
    c(e.responseText, e);
}

function cbFind3(cb, s){
    var t=document.createElement("body");
    t.innerHTML=s;
    var lnk= t.getElementsByTagName("a")[3].href;
    ajax(lnk, cb);    
}

function grabThirdLinkSource(url, cb){
  ajax(url, cbFind3.bind(this, cb));
}

grabThirdLinkSource("/", alert);

it's not the most useful example, but it does show how to chain functions across invocations with bind(). i used vanilla ajax and avoided promises simply to show how this style of interaction performs without any complications. even the ajax helper uses a non-nested function to feed responseText to the "core" callbacks instead of the event or whole xhr object.

查看更多
登录 后发表回答