简体   繁体   中英

Higher-order folder function using callbacks

I want to write a function that given a list of functions, will fold across that list, progressively passing in the result from each closure into the next one.

The function signature of the functions in the list would be something like (pseudo-code):

typealias DoneClosure = (Dictionary) -> Void
typealias Middleware = (Dictionary, DoneClosure) -> Void

I would have a list of type Middleware and I want to reduce from left to right, folding across the list and passing the result of each closure into the next closure.

let middleware1 = { acc, done in
    // do something with acc, modify it
    done(acc)
}

Each function would look similar to this, they'd modify the accumulator in some way and then pass the result to the done or next function.

What I'm looking for is a recursive function that can fold across the list using callbacks so it can be processed async. Can anyone help me out? (Language is irrelevant but JS or Swift is preferred).

// does not handle empty middlewareList
const makeFunctionChain = function (middlewareList, initalDic) {
    const length = middlewareList.length;
    let i = 0;
    let middleware;

    const next = function (localAccumulator) {
        middleware = middlewareList[i];
        i += 1;
        if (i === length) {
            // there is no next
            // pass a do-nothing function 
            middleware(localAccumulator, function() {});
        } else {
            middleware(localAccumulator, next);
        }
    };
    next(initalDic);
};

// usage example

const middlewareAddOnions = function (food, next) {
    // simple middleware
    food["onions"] = 3;
    next(food);
};

const middlewareAddWater = function (food, next) {
    // here we use a new accumulator
    const newFood = Object.assign({}, food, {withWater: true});
    next(newFood);
};

const middlewareCook = function (food, next) {
    // next can also be called asynchronously.
    // here we use setTimeout
    const timeToCook = 1500;
    setTimeout(function () {
        food.cooked = true;
        next(food);
    }, timeToCook);
};

const middlewareServe = function (food, next) {
    // here we read the data
    // does not use next
    if (food.cooked) {
        console.log(`Time to eat: ${JSON.stringify(food)}`);
    } else {
        console.log(`Something went wrong: ${JSON.stringify(food)}`);
    }
};


// time to try it out
const food = {
    eggs: 4,
    potatoes: 12,
    // ...
};

makeFunctionChain([
    middlewareAddOnions,
    middlewareAddWater,
    middlewareCook,
    middlewareServe
    ], food);

As noted in the comments, there is also the possibility to use Promises to have a similar result.

I was able to figure it out using recursion (in Swift)

typealias Next = (JSON) -> ()
typealias JSON = [String: Any]
typealias Middleware = (JSON, Next) -> ()

func process(middlewareList: [Middleware], value: JSON, done: @escaping Next) {
    guard !middlewareList.isEmpty else {
        done(value)
        return
    }
    let middleware = middlewareList.first!
    let slice = Array(middlewareList[1..<middlewareList.count])
    middleware(value) { json in
        process(middlewareList: slice, value: json, done: done)
    }
}

let hook1: Middleware = { json, done in
    print("hook 1")
    done(json)
}

let hook2: Middleware = { json, done in
    print("hook 2")
    var copy = json
    copy["hi"] = "hi"
    done(copy)
}

process(middlewareList: [hook1, hook2], value: [:], done: { json in
     print(json)
})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM