What does the use function do

  • Let’s take a look at the source code and ignore the fault-tolerant code. We’re doing a push of our own FN function into this.middleware.
app.use = function(fn){
  if (!this.experimental) {
    // es7 async functions are not allowed,
    // so we have to make sure that `fn` is a generator function
    assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
  }
  debug('use %s', fn._name || fn.name || The '-');
  this.middleware.push(fn);
  return this;
};
Copy the code

How does Middleware work

  • The application. Js file in koA is the initial execution file, which exposes an application constructor. Normally, when we new koa (), the constructor will initialize some properties inside. The Middleware array mentioned in the previous section is also in the constructor’s initialization property. Here is the source code snippet
function Application() {
  if(! (this instanceof Application)) return new Application;
  this.env = process.env.NODE_ENV || 'development';
  this.subdomainOffset = 2;
  this.middleware = [];
  this.proxy = false;
  this.context = Object.create(context);
  this.request = Object.create(request);
  this.response = Object.create(response);
}
Copy the code
  • Listen is bound to the Application constructor prototype. Let’s see what listen does. It creates a native server for Node. Focus on what’s going on inside this.callback, because we know that any request from the client will trigger the callback in http.createServer
app.listen = function(){
  debug('listen');
  var server = http.createServer(this.callback());
  return server.listen.apply(server, arguments);
};
Copy the code
  • Compose (this.middleware) : compose(compose(this.middleware)) Koa-compose is a package that the KOA project relies on, koA-compose2.x.
  • For example, compose returns a generator function that takes the argument next
  • The first step is to determine if next has an empty generator object, and if it does not, it will default to a generator object (noop).
  • The second step is to get the size of the Middleware array
  • The third step is to loop through the middleware array in reverse order, calling the functions in turn and passing in a generator object from the previous middleware execution as a parameter to the next middleware. Next Records the generator object after function* () {} was executed last time.
  • The last step is return yield *next, which is a neat use of yield syntax because it loops in reverse order and returns the generator object that was returned after the first middleware function call.
app.callback = function(){
  if (this.experimental) {
    console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')}/ / the key
  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));
  var self = this;

  if (!this.listeners('error').length) this.on('error'.this.onerror);

  return function handleRequest(req, res){
    varctx = self.createContext(req, res); self.handleRequest(ctx, fn); }};Copy the code
function compose(middleware){
  return function* (next){
    if(! next) next = noop();var i = middleware.length;

    while (i--) {
      next = middleware[i].call(this, next);
    }

    return yield*next; }}/**
 * Noop.
 *
 * @api private* /

function *noop(){}
Copy the code

Summary: Compose (this.middleware) returns a “generator” function for composing (this. Middleware)

– TJ Co library (core part)

  • Continuing with the section, the co.wrap() argument is a generator function. Now let’s look at the source code of the CO library

  • Co.wrap () returns a function called createPromise, which internally calls the CO function (the core of the CO library), Call fn.apply(this, arguments) to the generator function that compose just returned and pass in the co function.

co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn;
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this.arguments)); }};Copy the code
  • Comcompose returns a generator object (gen) for the generator object that calls fn.apply(this, arguments). The co function returns a Promise, mainly look at the internal code of the Promise callback function. First, some fault-tolerant judgments are made (which can be ignored for the moment), and then the onFulfilled function is fulfilled, which mainly performs two things. Gen. Next (res) performs our yield on middleware
function co(gen) {
  var ctx = this;
  var args = slice.call(arguments.1);

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if(! gen ||typeofgen.next ! = ='function') return resolve(gen);

    onFulfilled();

    / * * *@param {Mixed} res
     * @return {Promise}
     * @api private* /

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }

    / * * *@param {Error} err
     * @return {Promise}
     * @api private* /

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private* /

    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"')); }}); }Copy the code
  • Next (ret) calls next, and next first makes a judgment inside that if it’s the last yield it returns success, and then it calls toPromise, and let’s see what toPromise does, it basically makes some Promise type judgments, The point is that if (isGeneratorFunction (obj) | | isGenerator (obj)) return co., a call (this, obj);
function toPromise(obj) {
  if(! obj)return obj;
  if (isPromise(obj)) return obj;
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  if ('function'= =typeof obj) return thunkToPromise.call(this, obj);
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}
Copy the code
  • If it is a generator, the co function is being reexecuted, which is a recursive call.
  • Our middleware function looks something like this
app.use(function* f1(next) {
  console.log('f1: pre next');
  yieldnext; }); hereyieldNext went to the CO libraryif (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(thisObj) this code comes to re-traverse the logic of the CO function, that is, execute the right to the next middlewareCopy the code
  • Finally, let’s look at this code
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
Copy the code

If the Promise is returned, the success and error callback will be made. That is why the middleware function passed in will return a Promise after yield. If the Promise is fulfilled, ondepressing will be called again, and the next Gen. next() will be executed. This is why generator functions can automatically run down, as I know generator functions must be manually gen.next() to continue down.

  • Upload code to understand
new Promise(function(resolve, reject) {
  // I am middleware 1
  yield new Promise(function(resolve, reject) {
    // I am middleware 2
    yield new Promise(function(resolve, reject) {
      // I am middleware 3
      yield new Promise(function(resolve, reject) {
        / / I am a body
      });
      // I am middleware 3
    });
    // I am middleware 2
  });
  // I am middleware 1
});
Copy the code

conclusion

  • In fact, the generator pauses the execution logic of the function to realize the effect of waiting for the middleware, and listens to the promise to trigger the continuation of the function logic. The so-called backflow is just synchronous execution of the next middleware.

First middleware code execution stopped in this half, triggered the second execution of middleware, the second half the middleware execution stopped in this, trigger the execution of the third middleware, and then,,,,,,,, the first two middleware as the middleware, a second order middleware three middleware,,,,,,,,, all the third middleware has been completed The second middleware continues to execute the subsequent code, the second middleware code is fully executed, the first middleware subsequent code is executed, and then the end

Stole a map