Giter Site home page Giter Site logo

qianlongo / resume-native Goto Github PK

View Code? Open in Web Editor NEW
393.0 13.0 96.0 1.08 MB

一个用原生js写的会动的简历

Home Page: https://qianlongo.github.io/resume-native/dist/

License: MIT License

JavaScript 90.61% HTML 4.30% CSS 5.09%
javascript resume

resume-native's Introduction

谢谢你来啦

也许你我素未谋面,但很可能相见恨晚。希望这里能成为你的栖息之地,我愿和你一起收获喜悦,奔赴成长。

这里每年都会分享不少于200+篇精选优质好文,如果你想第一时间获取文章内容,可以前往【公众号】,也可以加我【微信】噢。

微信群 公众号 公众号 公众号 投稿

简要说明

一个好玩的会动的简历 O(∩_∩)O哈哈~,通过callbackpromisegeneratorasync四种方式分别实现。

点击预览

异步流程控制说明

可以切换到以下不同的分支来查看,简历生成过程中异步流程控制解决方案

  1. master(使用回调函数处理)
  2. promise(使用promise处理)
  3. generator-thunk(使用generator + thunk函数处理)
  4. generator-promise(使用generator + promise处理)
  5. async(使用async处理)

install

yarn i

run

npm run dev

preview

localhost:8080

or

127.0.0.1:8080

or

0.0.0.0:8080

build

npm run build

co源码分析

其实开始对generator感兴趣,正是由写这个会动的简历开始,一步步弄明白了怎么使用,以及通过它来解决一些异步流程控制问题。而co这个东西是什么呢?其正是解决上述问题的非常好的方案。所以打算写一篇走一步再走一步,揭开co的神秘面纱源码分析的文章。放在这个仓库里,也是有一定的纪念意义。

koa源码分析(进行中)

最近的工作涉及到编写nodejs的事情,而使用的框架正是横向同事基于koa而搭建的,虽然平时使用起来没有什么问题,但是总想整明白框架本身到底是怎么回事,既然框架本身是基于koa而搭建,那么先熟悉和了解koa的源码,再去看同事搭建的框架应该会更好,说干就干...

你知道koa中间件执行原理吗?

resume-native's People

Contributors

qianlongo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

resume-native's Issues

你知道koa中间件执行原理吗?

前言

原文地址

最近几天花了比较长的时间在koa(1)的源码分析上面,初次看的时候,被中间件执行那段整的晕乎乎的,完全不知道所以,再次看,好像明白了些什么,再反复看,我去,简直神了,简直泪流满面,简直丧心病狂啊!!!

koa

用在前面

下面的例子会在控制台中打印出一些信息(具体打印出什么?可以猜猜😀),然后返回hello world

let koa = require('koa')
let app = koa()

app.use(function * (next) {
  console.log('generate1----start')
  yield next
  console.log('generate1----end')
})

app.use(function * (next) {
  console.log('generate2----start')
  yield next
  console.log('generate2----end')
  this.body = 'hello world'
})

app.listen(3000)

用过koa的同学都知道添加中间件的方式是使用koa实例的use方法,并传入一个generator函数,这个generator函数可以接受一个next(这个next到底是啥?这里先不阐明,在后面会仔细说明)。

执行use干了嘛

这是koa的构造函数,为了没有其他信息的干扰,我去除了一些暂时用不到的代码,这里我们把目光聚焦在middleware这个数组即可。

function Application() {
  // xxx
  this.middleware = []; // 这个数组就是用来装一个个中间件的
  // xxx
}

接下来我们要看use方法了

同样去除了一些暂时不用的代码,可以看到每次执行use方法,就把外面传进来的generator函数push到middleware数组中

app.use = function(fn){
  // xxx
  this.middleware.push(fn);
  // xxx
};

好啦!你已经知道koa中是预先通过use方法,将请求可能会经过的中间件装在了一个数组中。

接下来我们要开始本文的重点了,当一个请求到来的时候,是怎样经过中间件,怎么跑起来的

首先我们只要知道下面这段callback函数就是请求到来的时候执行的回调即可(同样尽量去除了我们不用的代码)

app.callback = function(){
  // xxx

  var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));

  // xxx

  return function(req, res){
    // xxx

    fn.call(ctx).then(function () {
      respond.call(ctx);
    }).catch(ctx.onerror);

    // xxx
  }
};

这段代码可以分成两个部分

  1. 请求前的中间件初始化处理部分
  2. 请求到来时的中间件运行部分

我们分部分来说一下

var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));

这段代码对experimental做了下判断,如果设置为了true那么koa中将可以支持传入async函数,否则就执行co.wrap(compose(this.middleware))

只有一行初始化中间件就做完啦?

我知道koa很屌,但也别这么屌好不好,所以说评价一个好的程序员不是由代码量决定的

我们来看下这段代码到底有什么神奇的地方

compose(this.middleware)

把装着中间件middleware的数组作为参数传进了compose这个方法,那么compose做了什么事呢?其实就是把原本毫无关系的一个个中间件给首尾串起来了,于是他们之间就有了千丝万缕的联系。

function compose(middleware){
  return function *(next){
    // 第一次得到next是由于*noop生成的generator对象
    if (!next) next = noop(); 

    var i = middleware.length;
    // 从后往前开始执行middleware中的generator函数
    while (i--) {
      // 把后一个中间件得到的generator对象传给前一个作为第一个参数存在
      next = middleware[i].call(this, next);
    }
    
    return yield *next;
  }
}

function *noop(){}

文字解释一下就是,compose将中间件从最后一个开始处理,并一直往前直到第一个中间件。其中非常关键的就是将后一个中间件得到generator对象作为参数(这个参数就是文章开头说到的next啦,也就是说next其实是一个generator对象)传给前一个中间件。当然最后一个中间件的参数next是一个空的generator函数生成的对象。

我们自己来写一个简单的例子说明compose是如何将多个generator函数串联起来的

function * gen1 (next) {
  yield 'gen1'
  yield * next // 开始执行下一个中间件
  yield 'gen1-end' // 下一个中间件执行完成再继续执行gen1中间件的逻辑
}

function * gen2 (next) {
  yield 'gen2'
  yield * next // 开始执行下一个中间件
  yield 'gen2-end' // 下一个中间件执行完成再继续执行gen2中间件的逻辑
}

function * gen3 (next) {
  yield 'gen3'
  yield * next // 开始执行下一个中间件
  yield 'gen3-end' // 下一个中间件执行完成再继续执行gen3中间件的逻辑
}

function * noop () {}

var middleware = [gen1, gen2, gen3]
var len = middleware.length
var next = noop() // 提供给最后一个中间件的参数

while(len--) {
  next = middleware[len].call(null, next)
}

function * letGo (next) {
  yield * next
}

var g = letGo(next)

g.next() // {value: "gen1", done: false}
g.next() // {value: "gen2", done: false}
g.next() // {value: "gen3", done: false}
g.next() // {value: "gen3-end", done: false}
g.next() // {value: "gen2-end", done: false}
g.next() // {value: "gen1-end", done: false}
g.next() // {value: undefined, done: true}

看到了吗?中间件被串起来之后执行的顺序是

gen1 -> gen2 -> gen3 -> noop -> gen3 -> gen2 -> gen1

从而首尾相连,进而发生了关系😈。

co.wrap

通过compose处理后返回了一个generator函数。

co.wrap(compose(this.middleware))

所有上述代码可以理解为

co.wrap(function * gen ())

好,我们再看看co.wrap做了什么,慢慢地一步步靠近了哦

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

可以看到co.wrap返回了一个普通函数createPromise,这个函数就是文章开头的fn啦。

var fn = this.experimental
    ? compose_es7(this.middleware)
    : co.wrap(compose(this.middleware));

中间件开始跑起来啦

前面已经说完了,中间件是如何初始化的,即如果由不相干到关系密切了,接下来开始说请求到来时,初始化好的中间件是怎么跑的。

fn.call(ctx).then(function () {
  respond.call(ctx);
}).catch(ctx.onerror);

这一段便是请求到来手即将要经过的中间件执行部分,fn执行之后返回的是一个Promise,koa通过注册成功和失败的回调函数来分别处理请求。

让我们回到

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

createPromise里面的fn就是经过compose处理中间件后返回的一个generator函数,那么执行之后拿到的就是一个generator对象了,并把这个对象传经经典的co里面啦。如果你需要对co的源码了解欢迎查看昨天写的走一步再走一步,揭开co的神秘面纱,好了,接下来就是看co里面如何处理这个被compose处理过的generator对象了

再回顾一下co

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 || typeof gen.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);
    }

    /**
     * @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) + '"'));
    }
  });
}

我们直接看一下onFulfilled,这个时候第一次进co的时候因为已经是generator对象所以会直接执行onFulfilled()

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

gen.next正是用于去执行中间件的业务逻辑,当遇到yield语句的时候,将紧随其后的结果返回赋值给ret,通常这里的ret,就是我们文中说道的next,也就是当前中间件的下一个中间件。

拿到下一个中间件后把他交给next去处理

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) + '"'));
}

当中间件执行结束了,就把Promise的状态设置为成功。否则就将ret(也就是下一个中间件)再用co包一次。主要看toPromise的这几行代码即可

function toPromise(obj) {
  // xxx
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  // xxx
}

注意噢toPromise这个时候的返回值是一个Promise,这个非常关键,是下一个中间件执行完成之后回溯到上一个中间件中断执行处继续执行的关键

function next(ret) {
  // xxx
  var value = toPromise.call(ctx, ret.value);
  // 即通过前面toPromise返回的Promise实现,当后一个中间件执行结束,回退到上一个中间件中断处继续执行
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 
  // xxx 
}

看到这里,我们可以总结出,几乎koa的中间件都会被co给包装一次,而每一个中间件又可以通过Promise的then去监测其后一个中间件是否结束,后一个中间件结束后会执行前一个中间件用then监听的操作,这个操作便是执行该中间件yield next后面的那些代码

打个比方:

当koa中接收到一个请求的时候,请求将经过两个中间件,分别是中间件1中间件2

中间件1

// 中间件1在yield 中间件2之前的代码

yield 中间件2

// 中间件2执行完成之后继续执行中间件1的代码

中间件2

// 中间件2在yield noop中间件之前的代码

yield noop中间件

// noop中间件执行完成之后继续执行中间件2的代码

那么处理的过程就是co会立即调用onFulfilled来执行中间件1前半部分代码,遇到yield 中间件2的时候得到中间件2generator对象,紧接着,又把这个对象放到co里面继续执行一遍,以此类推下去知道最后一个中间件(我们这里的指的是那个空的noop中间件)执行结束,继而马上调用promise的resolve方法表示结束,ok,这个时候中间件2监听到noop执行结束了,马上又去执行了onFulfilled来执行yield noop中间件后半部分代码,好啦这个时候中间件2也执行结束了,也会马上调用promise的resolve方法表示结束,ok,这个时候中间件1监听到中间件2执行结束了,马上又去执行了onFulfilled来执行yield 中间件2后半部分代码,最后中间件全部执行完了,就执行respond.call(ctx);

啊 啊 啊好绕,不过慢慢看,仔细想,还是可以想明白的。用代码表示这个过程有点类似

new Promise((resolve, reject) => {
  // 我是中间件1
  yield new Promise((resolve, reject) => {
    // 我是中间件2
    yield new Promise((resolve, reject) => {
      // 我是body
    })
    // 我是中间件2
  })
  // 我是中间件1
});


中间件执行顺序

结尾

罗里吧嗦说了一大堆,也不知道有没有把执行原理说明白。

如果对你理解koa有些许帮助,不介意的话,点击源码地址点颗小星星吧

如果对你理解koa有些许帮助,不介意的话,点击源码地址点颗小星星吧

如果对你理解koa有些许帮助,不介意的话,点击源码地址点颗小星星吧

源码地址

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.