本文原创,转载请注明出处https://i.cnblogs.com/EditPosts.aspx?postid=5710639

  我们大家都知道,当koa接到请求经过中间件时,当执行到 yield next 语句时,Koa 暂停了该中间件,继续执行下一个符合请求的中间件('downstrem'),然后控制权再逐级返回给上层中间件('upstream')。我们下面分析其原理。

一、中间件级联

  当我们使用app.use()注册中间件时,它只是将各个中间件压进一个队列,但是这些中间件又是怎么级联的呢?

/**
* Expose compositor.
*/ module.exports = compose; /**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
/**
* middleware为中间件数组
* compose函数将中间件数组中的中间件级联起来
*/
function compose(middleware){
return function *(next){
  //noop()返回一个空的generator对象
if (!next) next = noop(); var i = middleware.length;
  
  // 将后一个中间件generator对象当做参数传递给前一个中间件
// 最后next为第一个generator对象
while (i--) {
next = middleware[i].call(this, next);
}
  
  // 最后用yield*将next generator对象引进来
return yield *next;
}
} /**
* Noop.
*
* @api private
*/ function *noop(){}

  看了上述代码和注释大家应该明白了,原来koa中的中间件是通过generator对象参数传递的方式级联的。下面我们研究他们的执行过程。

二、执行过程

1.大致过程

// 创建server并监听端口
app.listen = function(){
debug('listen');
var server = http.createServer(this.callback());
return server.listen.apply(server, arguments);
}; 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.')
}
var fn = this.experimental
? compose_es7(this.middleware)
: co.wrap(compose(this.middleware));//这里就是我们上面讲的compose()函数
var self = this; if (!this.listeners('error').length) this.on('error', this.onerror); return function(req, res){
res.statusCode = 404;
var ctx = self.createContext(req, res);
onFinished(res, ctx.onerror);
fn.call(ctx).then(function () {
respond.call(ctx);
}).catch(ctx.onerror);
}
};

2.co.wrap()

co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
};

  这里相当于调用了co()方法,把我们之前的compose()函数返回的结果函数作为参数传给了它。

3.co()——逆向执行关键

/**
* slice() reference.
*/ var slice = Array.prototype.slice; /**
* Expose `co`.
*/ module.exports = co['default'] = co.co = co; /**
* Wrap the given generator `fn` into a
* function that returns a promise.
* This is a separate function so that
* every `co()` call doesn't create a new,
* unnecessary closure.
*
* @param {GeneratorFunction} fn
* @return {Function}
* @api public
*/ co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
return co.call(this, fn.apply(this, arguments));
}
}; /**
* Execute the generator function or a generator
* and return a promise.
*
* @param {Function} fn
* @return {Promise}
* @api public
*/ 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
//返回promise
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); /**
* @param {Mixed} res
* @return {Promise}
* @api private
*/ // promise成功时调用
// 调用resolve()时执行
function onFulfilled(res) {
var ret;
try {
// 调用gen.next,到达一个yield
ret = gen.next(res);
} catch (e) {
return reject(e);
}
// 将gen.next()返回值传入next()函数
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) {
// 如果generator函数执行完毕,调用resolve,执行上述fullfilled函数
// 并将ret.value传入
if (ret.done) return resolve(ret.value);
// 将ret.value转换成promise
// 转换函数在下面
var value = toPromise.call(ctx, ret.value);
// 监听promise的成功/失败
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) + '"'));
}
});
} /**
* Convert a `yield`ed value into a promise.
*
* @param {Mixed} obj
* @return {Promise}
* @api private
*/ 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;
} /**
* Convert a thunk to a promise.
*
* @param {Function}
* @return {Promise}
* @api private
*/ function thunkToPromise(fn) {
var ctx = this;
return new Promise(function (resolve, reject) {
fn.call(ctx, function (err, res) {
if (err) return reject(err);
if (arguments.length > 2) res = slice.call(arguments, 1);
resolve(res);
});
});
} /**
* Convert an array of "yieldables" to a promise.
* Uses `Promise.all()` internally.
*
* @param {Array} obj
* @return {Promise}
* @api private
*/ function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
} /**
* Convert an object of "yieldables" to a promise.
* Uses `Promise.all()` internally.
*
* @param {Object} obj
* @return {Promise}
* @api private
*/ function objectToPromise(obj){
var results = new obj.constructor();
var keys = Object.keys(obj);
var promises = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var promise = toPromise.call(this, obj[key]);
if (promise && isPromise(promise)) defer(promise, key);
else results[key] = obj[key];
}
return Promise.all(promises).then(function () {
return results;
}); function defer(promise, key) {
// predefine the key in the result
results[key] = undefined;
promises.push(promise.then(function (res) {
results[key] = res;
}));
}
} /**
* Check if `obj` is a promise.
*
* @param {Object} obj
* @return {Boolean}
* @api private
*/ function isPromise(obj) {
return 'function' == typeof obj.then;
} /**
* Check if `obj` is a generator.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/ function isGenerator(obj) {
return 'function' == typeof obj.next && 'function' == typeof obj.throw;
} /**
* Check if `obj` is a generator function.
*
* @param {Mixed} obj
* @return {Boolean}
* @api private
*/ function isGeneratorFunction(obj) {
var constructor = obj.constructor;
if (!constructor) return false;
if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true;
return isGenerator(constructor.prototype);
} /**
* Check for plain object.
*
* @param {Mixed} val
* @return {Boolean}
* @api private
*/ function isObject(val) {
return Object == val.constructor;
}

  注意,我们在写每个中间件时,实际都有yield next;onFulfilled这个函数只在两种情况下被调用,一种是调用co的时候执行,还有一种是当前promise中的所有逻辑都执行完毕后执行  

  这里我们传入的fn是一个generator对象,根据上述转换函数,将会继续调用co()函数,执行next()时,我们传入的参数ret.val是下一个中间件的generator对象,所以继续调用co()函数,如此递归的执行下去;当到最后一个中间件时,执行完成后,ret.done==true,会再次调用resolve,返回到上一层中间件。

  这个过程其实就是递归调用的过程。

koa执行过程原理分析的更多相关文章

  1. Hive(六)hive执行过程实例分析与hive优化策略

    一.Hive 执行过程实例分析 1.join 对于 join 操作:SELECT pv.pageid, u.age FROM page_view pv JOIN user u ON (pv.useri ...

  2. Web APi之过滤器执行过程原理解析【二】(十一)

    前言 上一节我们详细讲解了过滤器的创建过程以及粗略的介绍了五种过滤器,用此五种过滤器对实现对执行Action方法各个时期的拦截非常重要.这一节我们简单将讲述在Action方法上.控制器上.全局上以及授 ...

  3. Python程序的执行过程原理(解释型语言和编译型语言)

    Python是一门解释型语言?我初学Python时,听到的关于Python的第一句话就是Python是一门解释型语言,我就这样一直相信下去,直到发现.pyc文件的存在,如果真是解释型语言,那么生成的. ...

  4. Hadoop MapReduce执行过程实例分析

    1.MapReduce是如何执行任务的?2.Mapper任务是怎样的一个过程?3.Reduce是如何执行任务的?4.键值对是如何编号的?5.实例,如何计算没见最高气温? 分析MapReduce执行过程 ...

  5. Hive学习之路 (二十)Hive 执行过程实例分析

    一.Hive 执行过程概述 1.概述 (1) Hive 将 HQL 转换成一组操作符(Operator),比如 GroupByOperator, JoinOperator 等 (2)操作符 Opera ...

  6. Hive(九)Hive 执行过程实例分析

    一.Hive 执行过程概述 1.概述 (1) Hive 将 HQL 转换成一组操作符(Operator),比如 GroupByOperator, JoinOperator 等 (2)操作符 Opera ...

  7. 关于 [栈溢出后jmp esp执行shellcode] 原理分析

    原文地址:https://blog.csdn.net/lixiangminghate/article/details/53333710 正常情况下,函数栈分布图如下: 即,返回地址被改为一段缓存区的地 ...

  8. 【Mybatis】SQL语句的解析执行过程原理

    sqlSession简单介绍 拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor),这 ...

  9. SpringMVC(关于HandlerMapping执行流程原理分析)

    请求过来先碰见中央调度器(前端调度器) //Determine handler for the current request; 对当前请求决定交给哪个handler, 当前请求地址过来 处理器执行链 ...

随机推荐

  1. 用swift写的一款小游戏,模仿的僵尸危机

    https://github.com/123456qwer/WDZombies.git   //git地址

  2. [leetcode]5. Longest Palindromic Substring最长回文子串

    Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...

  3. Pandas plot出图

    1.创建一个Series 这是一个线性的数据,我们随机生成1000个数据,Series 默认的 index 就是从0开始的整数,但是这里我显式赋值以便让大家看的更清楚 >>> imp ...

  4. 原子性 CAS算法

    一. i++ 的原子性问题 1.问题的引入: i++ 的实际操作分为三个步骤:读--改--写 实现线程,代码如下: public class AtomicDemo implements Runnabl ...

  5. VMWare 14.1 15 Pro 安装 macOS Mojave 10.14.1系统 遇到的问题解决方案

    安装环境 WIN10VMware Workstation Pro 15.0.0 Build 10134415工具准备1.VMware Workstation Pro 15.0.0 Build 1013 ...

  6. nagios 报警参数

    host_notification_options: d = notify on DOWN host states, u = notify on UNREACHABLE host states r = ...

  7. Change the default MySQL data directory with SELinux enabled

    转载:https://rmohan.com/?p=4605 Change the default MySQL data directory with SELinux enabled This is a ...

  8. I/O dempo

    标准读取写入 package io_stream; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; i ...

  9. centos7通过yum安装JDK1.8

    安装之前先检查一下系统有没有自带open-jdk 命令: rpm -qa |grep java rpm -qa |grep jdk rpm -qa |grep gcj 如果没有输入信息表示没有安装. ...

  10. mysql---select的五种子句学习(where、group by、having、order by、limit)

      mysql---select的五种子句学习(where.group by.having.order by.limit) 分类: Mysql学习2012-09-27 16:14 1533人阅读 评论 ...