npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

frank-native-callstack

v1.0.0

Published

build call stack model for writing async like sync

Downloads

38

Readme

frank-native-callstack - Simplified Callback Hell.

Callback Hell is a common and difficult issue which need almost all JavaScript programmers to face it thousands of times.
This module can make it easy.

APIs:

  1. class: CallStack
    1. import & instantiate:
      var CallStack = require("frank-native-callstack");
      // instantiate by Constructor
      var callStack = new CallStack();
      // instantiate by class.newInstance
      var callStack = CallStack.newInstance();
      // instantiate by factory
      var callStack = require("frank-native-core").newInstance(CallStack);
    2. callStack.append: append a CallItem on callStack
      append({
          "vari":string,
          "methodCall":function,
          "paramsCall":function
      });
      append(variable:string, methodCall:function, paramsCall:function);
      append(new CallItem()
      .variKey(key:string)
      .methodCall(call:function)
      .paramsCall(call:function));
    3. callStack.next: append a CallItem after current executing CallItem
      [The arguments types are the same as append]
    4. callStack.val: get value from callStack cache
      val(key:string)
    5. callStack.success: set success callback for all success.
      success(cb:function)
    6. callStack.error: set error callback for any Error happening.
      error(cb:function)
    7. callStack.complete: set complete callback for callStack completing.
      complete(cb:function)
    8. callStack.execute: execute this callStack.
      execute()
    9. callStack.loop: jump to the loopFromCallItem if the conditionCall() is true
      loop(conditionCall:function, loopFromCall:function)
    10. callStack.sleep: add the timeout of last callItem executing.
      sleep(timeoutMillisecond:number)
  2. class: CallItem
    1. import & instantiate

      var CallItem = require("frank-native-callstack/CallItem");
      // instantiate by Constructor
      var callItem = new CallItem();
      // instantiate by class.newInstance
      ...
      // instantiate by factory
      ...
    2. All functions are chain model and Getter & Setter, Such as:

      var ci = new CallItem()
          .variKey("key")
          .methodCall((params,receive)=>receive(params))
          .paramsCall(()=>["params"]);
      // string: "key"
      var key = ci.variKey(); 
      // function:(params,receive)=>receive(params)
      var methodCall = ci.methodCall();
      // function:()=>["params"]
      var paramsCall = ci.paramsCall();
    3. callItem.variKey: the name of callItem result to save as in callStack

      variKey(key:string)
    4. callItem.methodCall: method for execute

      methodCall(call:function)
    5. callItem.paramsCall: parameter for method

      paramsCall(call:function)
    6. callItem.next : the next CallItem instance

      next(item:CallItem)
    7. callItem.loopCall: [function]: loop condition

      loopCall(call:function)
    8. callItem.loopFrom: [CallItem]: go to the callItem when done and condition is true.

      loopFrom(item:CallItem)
  3. others:
    1. CallStack.o
      for writing multilevel JSON in one line.
      • Example:
      var arg = {
          a1:0,
          a2:{
              b1:1
          },
          a3:{
              b2:2
          }
      };
      • can be write as:
      var o = CallStack.o;
      var arg = o().kv("a1",0).kv("a2.b1",1).kv("a3.b2",2).done();
      • or as:
      var o = CallStack.o;
      var arg = o("a1",0).kv("a2.b1",1).done("a3.b2",2);
    2. CallStack.ASSIGN
      Using it if the logic is just to store value. Easy to use.
      • For example:
      new CallStack().append("var1",CallStack.ASSIGN,()=>[1]);
      • It's equivalent to:
      new CallStack().append("var1",(p,r)=>r(p),()=>[1]);

#Super simple to use: ##Example:

First of all:

Let us suppose that there only 4 async functions and no other math functions for further example.

var asyncAdd = (p1,p2,cb)=>setTimeout(()=>cb(p1+p2));
var asyncMns = (p1,p2,cb)=>setTimeout(()=>cb(p1-p2));
var asyncMul = (p1,p2,cb)=>setTimeout(()=>cb(p1*p2));
var asyncDvd = (p1,p2,cb)=>setTimeout(()=>cb(p1/p2));

Second: Use "callStack.append" to handle simple logic

Calculate the result of formula ((1+2)*3+4)/5-9+2)
  1. Traditional
    asyncAdd(1, 2, res => { // =>1+2 ------------------------------------------ (3)
        try {
            asyncMul(res, 3, res => { // =>*3 --------------------------------- (9)
                try {
                    asyncAdd(res, 4, res => { // =>+4 ------------------------ (13)
                        try {
                            asyncDvd(res, 5, res => { // =>/5 --------------- (2.6)
                                try {
                                    asyncMns(res, 9, res => { // =>-9 -------(-6.4)
                                        try { 
                                            asyncAdd(res, 2, res => {// =>+2 (-4.4)
                                                try{
                                                    console.log("((1+2)*3+4)/5-9+2=" + res);
                                                }catfch(err){
                                                    console.error(err);
                                                }
                                            });
                                        } catch (err) {
                                            console.error(err);
                                        }
                                    });
                                } catch (err) {
                                    console.error(err);
                                }
                            });
                        } catch (err) {
                            console.error(err);
                        }
                    });
                } catch (err) {
                    console.error(err);
                }
            });
        } catch (err) {
            console.error(err);
        }
    });
  1. Use frank-native-callstack
    • Overview:
    new CallStack().append("res",asyncAdd,()=>[1,2])
    .append("res",asyncMul,function(){return [this.val("res"),3]})
    .append("res",asyncAdd,function(){return [this.val("res"),4]})
    .append("res",asyncDvd,function(){return [this.val("res"),5]})
    .append("res",asyncMns,function(){return [this.val("res"),9]})
    .append("res",asyncAdd,function(){return [this.val("res"),2]})
    .success(res =>console.log("((1+2)*3+4)/5-9+2=" + res))
    .error(err => console.error(err))
    .execute();
    • Dscription with comments:
    // 1. Instantiate a CallStack instance cs.
    var cs = new CallStack();
    // 2. append a CallItem instance
    cs.append(
    // 2.1. with resultName:"res"
    "res",
    // 2.2. with a call function: asyncAdd
    asyncAdd,
    // 2.3. with a parameters function: ()=>[1,2].
    //      means it returns params[0]: p1=1, params[1]: p2=2
    ()=>[1,2]);
    // 2. if not using lambda function,
    //    the scope 'this' is the CallStack instance cs.
    //    both in call function and parameter function.
    cs.append("res",asyncMul,function(){return [this.val("res"),3];});
    // 3. All functions in CallStack are designed as chained mode.
    cs.append("res",asyncAdd,()=>[cs.val("res"),4])
    .append("res",asyncDvd,()=>[cs.val("res"),5])
    .append("res",asyncMns,()=>[cs.val("res"),9])
    .append("res",asyncAdd,()=>[cs.val("res"),2])
    // 4. Success callback:
    // 4.1. Success callback will be call when 
    //      all CallItem instance has done and successed.
    // 4.2. The result of last CallItem instance 
    //      will be send as the argument of success callback.
    // 4.3. The scope 'this' is the CallStack instance cs.
    .success(res=>console.log(res));
    // 5. Error callback:
    // 5.1. Error callback will be call when any Error happends.
    // 5.2. 
    // 5.3. The scope 'this' is the CallStack instance cs.
    .error(err=>console.error(err));
    // 6. The CallStack instance will run after execute
    .execute();

Third: Use "callStack.loop" to control loop logic and flow.

    Calculate formula 1! + 2! + ... + 9! + 10!.
    By Traditional, it's hard to see loop logic clearly and must
nest serval functions to code.
    But by frank-native-core, can clearly see loop logic and no
need to nest.
  • Use frank-native-callstack:
var marks = {};
var cs = new CallStack();
// 1. sum = 0;
cs.append("sum", (p, r) => r(p), () => [0]);
// 2. i = 1;
cs.append("i", (p, r) => r(p), () => [1]);
// 3. fac = 1;
// let the stack just appended name
// "itemi", => fac = 1
// for loop logic jump back.
cs.append(marks.itemi = new CallItem().variKey("fac").methodCall((p, r) => r(p)).paramsCall(() => [1]));
// 4. j = 1;
cs.append("j", (p, r) => r(p), () => [1]);
// 5. fac = asyncMul(fac, j);
// let the stack just appended 
// name "itemj", => fac = fac * j
// for loop logic jump back.
cs.append(marks.itemj = new CallItem().variKey("fac").methodCall(asyncMul).paramsCall(() => [cs.val("fac"), cs.val("j")]));
// 6. j = asyncAdd(j, 1)
cs.append("j", asyncAdd, () => [cs.val("j"), 1]);
// 7. j <= i?back to itemj
cs.loop(() => cs.val("j") <= cs.val("i"), () => marks.itemj);
// 8. i = asyncAdd(i, 1)
cs.append("i", asyncAdd, () => [cs.val("i"), 1]);
// 9. sum = asyncAdd(sum, fac)
cs.append("sum", asyncAdd, () => [cs.val("sum"), cs.val("fac")]);
// 10. i <= 10?back to itemi
cs.loop(() => cs.val("i") <= 10, () => marks.itemi);
// 11. console sum value. all done.
cs.success((sum) => console.log("1! + 2! + 3! + 4! + 5! + 6! + 7! + 8! + 9! + 10! = " + sum));
// 12. console err if happends
cs.error((err) => console.error(err.message));
// execute CallStack cs.
cs.execute();
        Look, we can handle complicate async logics like sync. 
        There is no Call Hell any more.
        It likes the following sync logic.
    var sum = 0;// ------------------ <= step 1.
    var i = 1;// -------------------- <= step 2.
    while(true){
        var fac = 1;// -------------- <= step 3., mark it "itemi"
        var j = 1;// ---------------- <= step 4.
        while(true){
            fac = asyncMul(fac, j);// <= step 5., mark it "itemj"
            j = asyncAdd(j, 1);// --- <= step 6.
            if(j<=i){// ------------- <= step 7.
                continue;// --------- <= step 7., back to mark "itemj"
            }else{
                break;// ------------ <= step 7., go next
            }
        }
        i = async(i, 1);// ---------- <= step 8.
        sum = asyncAdd(sum, fac);// - <= step 9.
        if(i<=10){// ---------------- <= step 10.
            continue;// ------------- <= step 10., back to mark "itemi"
        }else{
            break;// ---------------- <= step 10., go next
        }
    }
    console.log(sum);// ------------- <= step 11.
    Acturely, coding it like "while(true)" and "if(condition){continue;}else{break;}"
    is for easy understanding, the sync style codes should be like the following codes.
    var sum = 0;
    var i = 1;
    while(i<=10){
        var fac = 1;
        var j = 1;
        while(j<=i){
            fac = asyncMul(fac, j);
            j = asyncAdd(j, 1);
        }
        sum = asyncAdd(sum, fac);
        i = async(i, 1);
    }
    console.log(sum);

Third: Use "callStack.next" to add a callItem after current executing callItem.

    Then someone meticulous may want to ask, it somewhat likes Promise. why don't
we choose Promise. 
    To a certain exten yes, Promise handles the Call Hell issue too. but the
defferences are: 
    1. CallStack has own variable scope. it likes a thread. Just think 
about Thread variables.
    2. A simpler way to coding success/error/complete handler callback. one
callStack only has one callback on each success/error/complete.
    3. The hugest difference is that CallStack can break logics into gradations.
  1. A web filter to controling data format of input and output.
// a function of reading params from Request
var readParams = function(req){...};
// a function of geting web action by Request
var filter = function(req,resp){
    var cs = new CallStack();
    cs.append("params",(p,r)=>r(p),()=>[readParams(req)]);
    cs.append("data",(p,r)=>r(p),()=>[{}]);
    cs.append(null,function(receive){
        if(req.path.indexOf("/product/save")){
            // use callStack.next to route to the 
            // source which req.path is pointing to.
            cs.next(null,require("./controller/product").save,()=>null);
        }else if{
            ...
        }else if{
            ...
        }else if{
            ...
        }else{
            throw new Error("Source not found");
        }
    },()=>null);
    cs.success(()=>resp.json({
        result:true,
        data:resp.json(),
        message:cs.val("message")
    }));
    cs.error((err)=>resp.json({
        result:false,
        message:err.message
    }));
    cs.execute();
}
  1. ./controller/product.js
module.exports.save = function(callback){
    var params = this.val("params");
    var data = this.val("data");

    // business of saving product start
    ...
    ...
    // detail logic of saving product
    var product = ...
    ...
    ...
    // business of saving product is done.

    // put result product into data using key "product"
    // then product will be exported to fronted named "product" in data.
    // that's called "break logics into gradations"
    data.product = product;
    // calling callback likes "return", means the function has accomplished
    // and it's time for next step.
    callback();
};

#Thanks for reading

The codes of demos are on file "./test/test.js".
If you have any questions, my email address is [email protected].