手搓一个类express中间件系统

133 阅读2分钟

题目 实现一个简单的中间件系统 描述 你是否使用过 Express Middleware ?

Middleware函数是指可以串联起来的,有特定interface的函数。

app.use('/user/:id', function (req, res, next) {
  next()
}, function (req, res, next) {
  next(new Error('sth wrong'))
})

请实现一个简单的middleware 系统!


type Request = object

type NextFunc =  (error?: any) => void

type MiddlewareFunc = 
  (req: Request, next: NextFunc) => void

type ErrorHandler = 
  (error: Error, req: Request, next: NextFunc) => void

class Middleware {
  use(func: MiddlewareFunc | ErrorHandler) {
    // do any async operations
    // call next() to trigger next function
  }
  start(req: Request) {
    // trigger all functions with a req object
  }
}

有了上述之后,我们可以支持类似Express的用法。

const middleware = new Middleware()

middleware.use((req, next) => {
   req.a = 1
   next()
})

middleware.use((req, next) => {
   req.b = 2
   next()
})


middleware.use((req, next) => {
   console.log(req)
})

middleware.start({})
// {a: 1, b: 2}

注意到 use() 支持ErrorHandler,ErrorHandler有3个参数,在发生Error或者next()被含参数调用的时候触发,比如这样。


const middleware = new Middleware()

// throw an error at first function
middleware.use((req, next) => {
   req.a = 1
   throw new Error('sth wrong') 
   // or `next(new Error('sth wrong'))`
})

// since error occurs, this is skipped
middleware.use((req, next) => {
   req.b = 2
})

// since error occurs, this is skipped
middleware.use((req, next) => {
   console.log(req)
})

// since error occurs, this is called
middleware.use((error, req, next) => {
   console.log(error)
   console.log(req)
})

middleware.start({})
// Error: sth wrong
// {a: 1}

答案 第一时间有点思路但没写出来,看了评论的解答

核心就是

  • 注意判断是处理错误的还是常规的(通过func.length判断)
  • try catch,next参数判断是否抛出错误,且要做错误处理

实现1

通过两个数组分清常规处理func,与错误处理func,分别维护对于索引,思路清晰

class Middleware {
  /**
   * @param {MiddlewareFunc} func
   */
  constructor() {
    this.callbacks = [];
    this.errorCallbacks = [];
  }
  use(func) {
    if (func.length == 2) {
      this.callbacks.push(func);
    } else {
      this.errorCallbacks.push(func);
    }
  }

  /**
   * @param {Request} req
   */
  start(req) {
    var i = 0;
    var req = req;
    var ei = 0;
    const self = this;
    function next(error) {
      var func;
      var args = [req, next]
      if (error) {
        func = self.errorCallbacks.shift();
        args = [error, ...args]
      } else {
        func = self.callbacks.shift();
      }
      try {
        func.apply(undefined, args);
      } catch (error) {
        next(error);
      }
    }
    next();
  }
}

实现2

一个队列实现

//摘自评论区: https://bigfrontend.dev/problem/create-a-middleware-system/discuss
class Middleware {
  /**
   * @param {MiddlewareFunc} func
   */
  constructor() {
    // Create a function queue to help with execution
    this.funcs = [];
    this.req = null;
  }
  use(func) {
    // Push the function into Queue
    this.funcs.push(func);
  }

  /**
   * @param {Request} req
   */
  start(req) {
    this.req = req;
    // Start the chain
    this.next();
  }

  next = (err) => {
    // take out the function to execute from the queue
    const toExecute = this.funcs.shift();
    // Catch execution error when a function throw an error
    try {
      // args length tells us if its a normal call or an error call
      if (toExecute.length === 2) {
        // there is no error, execute the function with current request and next()
        if (!err) {
          toExecute(this.req, this.next);
        }
        // There is an error, call next() immediately for error handling. This will now keep on dequeuing funs queue 
        // till we get an error handler function with 3 args 
        else{
          this.next(err);
        }
      }
      // There's an error and now we got a func with more than 2 args, i.e. an error handler
      else {
        toExecute(err, this.req, this.next);
      }
    }
    catch (e) {
      this.next(e);
    }

  }
}