You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functioncompose(middleware){if(!Array.isArray(middleware))thrownewTypeError('Middleware stack must be an array!')for(constfnofmiddleware){if(typeoffn!=='function')thrownewTypeError('Middleware must be composed of functions!')}/** * @param {Object} context * @return {Promise} * @api public */returnfunction(context,next){// last called middleware #letindex=-1returndispatch(0)functiondispatch(i){if(i<=index)returnPromise.reject(newError('next() called multiple times'))index=iletfn=middleware[i]if(i===middleware.length)fn=nextif(!fn)returnPromise.resolve()try{returnPromise.resolve(fn(context,functionnext(){returndispatch(i+1)}))}catch(err){returnPromise.reject(err)}}}}
/** * slice() reference. */varslice=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;returncreatePromise;functioncreatePromise(){returnco.call(this,fn.apply(this,arguments));}};/** * Execute the generator function or a generator * and return a promise. * * @param {Function} fn * @return {Promise} * @api public */functionco(gen){varctx=this;varargs=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//返回promisereturnnewPromise(function(resolve,reject){if(typeofgen==='function')gen=gen.apply(ctx,args);if(!gen||typeofgen.next!=='function')returnresolve(gen);onFulfilled();/** * @param {Mixed} res * @return {Promise} * @api private */// promise成功时调用// 调用resolve()时执行functiononFulfilled(res){varret;try{// 调用gen.next,到达一个yieldret=gen.next(res);}catch(e){returnreject(e);}// 将gen.next()返回值传入next()函数next(ret);returnnull;}/** * @param {Error} err * @return {Promise} * @api private */functiononRejected(err){varret;try{ret=gen.throw(err);}catch(e){returnreject(e);}next(ret);}/** * Get the next value in the generator, * return a promise. * * @param {Object} ret * @return {Promise} * @api private */functionnext(ret){// 如果generator函数执行完毕,调用resolve,执行上述fullfilled函数// 并将ret.value传入if(ret.done)returnresolve(ret.value);// 将ret.value转换成promise// 转换函数在下面varvalue=toPromise.call(ctx,ret.value);// 监听promise的成功/失败if(value&&isPromise(value))returnvalue.then(onFulfilled,onRejected);returnonRejected(newTypeError('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 */functiontoPromise(obj){if(!obj)returnobj;if(isPromise(obj))returnobj;if(isGeneratorFunction(obj)||isGenerator(obj))returnco.call(this,obj);if('function'==typeofobj)returnthunkToPromise.call(this,obj);if(Array.isArray(obj))returnarrayToPromise.call(this,obj);if(isObject(obj))returnobjectToPromise.call(this,obj);returnobj;}/** * Convert a thunk to a promise. * * @param {Function} * @return {Promise} * @api private */functionthunkToPromise(fn){varctx=this;returnnewPromise(function(resolve,reject){fn.call(ctx,function(err,res){if(err)returnreject(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 */functionarrayToPromise(obj){returnPromise.all(obj.map(toPromise,this));}/** * Convert an object of "yieldables" to a promise. * Uses `Promise.all()` internally. * * @param {Object} obj * @return {Promise} * @api private */functionobjectToPromise(obj){varresults=newobj.constructor();varkeys=Object.keys(obj);varpromises=[];for(vari=0;i<keys.length;i++){varkey=keys[i];varpromise=toPromise.call(this,obj[key]);if(promise&&isPromise(promise))defer(promise,key);elseresults[key]=obj[key];}returnPromise.all(promises).then(function(){returnresults;});functiondefer(promise,key){// predefine the key in the resultresults[key]=undefined;promises.push(promise.then(function(res){results[key]=res;}));}}/** * Check if `obj` is a promise. * * @param {Object} obj * @return {Boolean} * @api private */functionisPromise(obj){return'function'==typeofobj.then;}/** * Check if `obj` is a generator. * * @param {Mixed} obj * @return {Boolean} * @api private */functionisGenerator(obj){return'function'==typeofobj.next&&'function'==typeofobj.throw;}/** * Check if `obj` is a generator function. * * @param {Mixed} obj * @return {Boolean} * @api private */functionisGeneratorFunction(obj){varconstructor=obj.constructor;if(!constructor)returnfalse;if('GeneratorFunction'===constructor.name||'GeneratorFunction'===constructor.displayName)returntrue;returnisGenerator(constructor.prototype);}/** * Check for plain object. * * @param {Mixed} val * @return {Boolean} * @api private */functionisObject(val){returnObject==val.constructor;}
运行流程
1.
new app()
首先koa中主要有
app
,context
,response
,request
四个基类,我们在实例化app的时候,实际上就是初始化了一些app中的属性。2.
app.listen()
在我们执行
app.listen()
时,主要做了createServer
,中间件用compose
串起来。这里面的
compose
是Koa洋葱圈模型的实现关键,后面会详细介绍3.handle request
当请求来临时,koa首先
Object.create(this.context)
,创建一个我们常用ctx对象,然后执行中间件,最后respond(res.end(body)
)洋葱圈的实现
我们知道,koa中的比较重要的部分在于其中间件的挂载和执行,当请求到来时,中间件先顺序执行,再逆序执行,那么这在koa2.x中是如何实现的呢?
1.app.use()
首先当我们执行app.use时,将该中间件push到middleware队列中。
2.app.listen()
当执行listen时,执行
const fn = compose(this.middleware);
,compose执行返回一个函数fn,fn执行时,按队列中的顺序依次执行,传入参数ctx及next方法。那么中间件为什么会从前向后执行,然后再从后向前执行呢?
首先,我们在写中间时会有
await next()
的用法(注意,await会等到后面的Promise resolve或reject后才厚向下继续执行),那么执行await next()
就会转而执行dispatch(i + 1)
,直到最后一个中间件;当执行到最后一个再执行dispatch(i + 1)
时,会触发if (!fn) return Promise.resolve()
,最后一个中间件开始执行await next()
后面的逻辑,完成后,执行倒数第二个,依次执行到第一个中间件。注意,当中间件中有两处
await next()
时,会触发if (i <= index) return Promise.reject(new Error('next() called multiple times'))
,抛出错误。context/request/response
三者的关系引用深入浅出koa中的一张图。
其中,我们在使用ctx.body等属性或方法时,实际上调用的this.request.body等属性或方法,实际实现就是调用了delegate库,将request和response中一些常用属性和方法挂载到context对象上。
中间件的书写
koa中间件实现起来比较简单,只要实现一个带有ctx和next参数的一个函数即可。以koa-body为例。随便看一个中间件就好了
Koa1.0中的洋葱圈实现
Koa1.0中的中间还没有await和async,而是用的yield来实现,
yeild next
如何做到上述的顺序执行然后逆序呢?我们下面简单回顾一下。1.compose middleware
2.co.wrap()
这里相当于调用了co()方法,把我们之前的compose()函数返回的结果函数作为参数传给了它。
3.co()——逆向执行关键
注意,我们在写每个中间件时,实际都有yield next;onFulfilled这个函数只在两种情况下被调用,一种是调用co的时候执行,还有一种是当前promise中的所有逻辑都执行完毕后执行
这里我们传入的fn是一个generator对象,根据上述转换函数,将会继续调用co()函数,执行next()时,我们传入的参数ret.val是下一个中间件的generator对象,所以继续调用co()函数,如此递归的执行下去;当到最后一个中间件时,执行完成后,ret.done==true,会再次调用resolve,返回到上一层中间件。
这个过程其实就是递归调用的过程。
The text was updated successfully, but these errors were encountered: