Implement blocking control flow in NodeJS

2019-03-31 20:17发布

问题:

I am trying to create a blocking control flow, but have hit a dead end. The idea is that a request comes in and flows through a number of handler functions. Each function does something and decides whether or not the request is now fulfilled. If it is, the handling stops, if not, the next handler is invoked. Each handler might or might not do something asynchronously (like fetching/writing data). If that is the case, then the following handlers depend on the data being there before being started.

So far, I have two ideas, which both do not quite match these requirements: 1) All handlers are functions, which are pushed into an array and iterated over with some . If a handler wishes to stop the control flow, it simply needs to return true. With this approach, I can't have any of the handlers invoking asynchronous functions.

2) All handlers are promises which are chained. This seems like a better idea to me, but I can't figure out how to best handle stopping the control flow. I have two ideas: - Keep a variable which is set to true once a handler decides to interrupt the flow. Check this value in every single handler and resolve immediately, if needed. This means a lot of duplicate code. - Rejecting a promise if it does not wish to continue the normal flow, and continuing in catch. However, the thought of using errors as a means of controlling the flow pains me - and also it means that I have to handle the case where the catch is invoked because of an actual error.

My spidey senses tell me there has to be another way, but I can't figure it out.

回答1:

Don't chain the promise handlers, nest them. You can do that programmatically from an array of functions:

var handlers = […]; // functions that can return promises

var run = handlers.reduceRight(function(next, handler) {
    return function() {
        return Promise.resolve(someArg).then(handler).then(function(result) {
            if (result) // true, whatever
                return result;
            else
                return next();
        });
    };
}, function end() {
    return Promise.reject(new Error("no handler matched"));
});

run().then(function(result) {
    // the result from the first handler that returned anything
}, function(err) {
    // some handler threw an exception, or there was no handler
});


回答2:

You can use ES6 generators to generate the promises as needed. Each time a promise resolves it could check for a stop value and if false (i.e. continue to the next handler) ask for the next from the iterator.

function* promiseHandlers() {
  const handlers = [
    promiseHandler1,
    promiseHandler2,
    promiseHandler3,
    promiseHandler4,
    promiseHandler5
  ];

  for (const handler of handlers) {
    yield handler();
  }
}

function run(iter) {
  const i = iter.next();
  if (i.done) {
    return Promise.reject(new Error("end of iterator reached"));
  }
  return Promise.resolve(i.value).then(result => result || run(iter));
}

run(promiseHandlers());

This isn't actually chaining though. Instead it would execute each promiseHandler in order but the results of those are not passed to the next. I think this is correct for what the OP wanted especially since a truthy result ends the iteration (i.e. break).