Node.js

一种事件循环架构

  • Node.js的异步事件驱动到底是什么?怎样工作的?
    • JavaScript 关键特性讲解
    • Node.js的事件队列
    • 用otto引擎实现一个Node.js
  • 在脉脉平台,目前我们前端是怎么用Node.js的?
  • Node.js擅长哪些领域

带着问题开始

JavaScript

high-level
object-oriented
just-in-time 

curly-bracket syntax

prototype-based object-orientation
dynamic typing
first-class functions

示例0.0:函数

var call = fn => fn(3)
var doSomething = (n) => {
  console.log(n)
}

// 函数可以作为参数传给传给函数
call(doSomething)

// 使用函数很方便,可以直接匿名使用
call(() => {
  console.log(2)
})

依赖宿主环境

Host

JavaScript Codes

Engine(v8...)

胶水语言

...

v8(c++)

otto(golang)

动态性极好

网络script资源

nodejs动态加载
两段不同的代码可以在不同时机被执行,且上下文可以互相侵入

示例0.1:侵入上下文

var a = 1
// eval 在当前作用域调用一段js代码
eval('a = 2')
console.log(a)

Jsonp

示例0.2:独立上下文

const vm = require('vm');

const x = 1;

const sandbox = { x: 2 };
vm.createContext(sandbox); // Contextify the sandbox.

const code = 'x += 40; var y = 17;';
// `x` and `y` are global variables in the sandboxed environment.
// Initially, x has the value 2 because that is the value of sandbox.x.
vm.runInContext(code, sandbox);

console.log(sandbox.x); // 42
console.log(sandbox.y); // 17

console.log(x); // 1; y is not defined.
  1. 灵活,迭代快,发版不受限
  2. 难维护,难排查
  3. 安全性差,性能差

衍生特点

Node.js

asynchronous event-driven

JavaScript runtime
不是lib不是框架,与浏览器并列,一种新的宿主环境

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

——nodejs.org

Node.js 能做什么?

asynchronous event-driven

示例1.0:异步事件、计时器

console.time('timeout')
// setTimeout第一个参数,是一个函数表达式
// 第二个参数时毫秒数,经过指定的延迟调用表达式
// 计时器是一种典型的宿主提供的能力
setTimeout(() => {
    // 100.671ms    
    // console.time/timeEnd是计时api
    console.timeEnd('timeout')
}, 100)

一个定时器就是一个异步事件

跟定时器类似,Node.js中存在多种异步事件,不同种事件的回调函数会加入不同的队列,这些队列之间存在优先级关系。事件以单线程的形式被排队调用

示例1.1:单线程

console.time('timeout')
setTimeout(() => {
    // 由于线程忙,所以即时时间到了,也只能排队
    // 1001.671ms    
    console.timeEnd('timeout')
}, 100)

// 线程阻塞
const block = ms => {
    const start = Date.now()
    while(Date.now() - start < ms) {}
}
block(1000)

// no data-race

示例1.2:形象演示队列优先级

const fs = require('fs')
const readable = fs.createReadStream('./text')

// poll
readable.on('data', () => {
    console.log('poll: ', Date.now())
})

// close
readable.on('close', () => {
    console.log('close: ', Date.now())
})

// timer
setTimeout(() => {
    console.log('timers: ', Date.now())
}, 1000)

// 当前线程阻塞
const block = ms => {
    const start = Date.now()
    while(Date.now() - start < ms) {}
}

block(3000)
// 开始处理事件队列的callback
console.log('todo queued jobs: ', Date.now())

// todo queued jobs:  1575476371564
// timers:  1575476371566
// poll:  1575476371570
// close:  1575476371571

otto引擎试玩

一个“nodejs”它是怎么工作的?

getting started

-> go version
go version go1.13.5 darwin/amd64
-> go get fknsrs.biz/p/ottoext
-> go get github.com/robertkrimen/otto
-> go get github.com/GeertJohan/go.rice
-> go get gopkg.in/readline.v1
-> cd ~/go/src/fknsrs.biz/p/ottoext/cmd/ottoext
-> go build main.go
-> ./main example.js

一个golang实现的“nodejs”的架构

otto

ottocmd

ottoext

大前提:otto能做什么?

执行js脚本

向js注入函数

获取并执行js方法

准备js与宿主环境交互的“钩子”,如timer,网络请求...这部分在ottoext的各种包中定义

在准备了各种与宿主环境的钩子之后,它已经是一个成熟的js runtime了,在后面的js脚本中就可以使用宿主注入的方法了,这部分代码在ottocmd

根据异步的特点,宿主环境提供的“钩子”往往是需要回调的,引擎需要获取回调函数,并且可以执行它,这部分在ottoext/loop

大前提:otto能做什么?(2)

1)

2)

3)

回想一下刚刚提到的nodejs的核心特征

asynchronous event-driven

EventLoop (1 数据结构)

EventLoop (2 循环运行)

EventLoop (3 执行任务)

参考源码

Node.js在平台的应用

  1. 客服平台

  2. 周报平台

  3. 开发者平台

  4. ...

主要技术选型

  1. koa

  2. mongodb

  3. pm2

koa

洋葱模型

例 1.0 koa服务器 - 洋葱模型

const Koa = require('koa');
const app = new Koa();

// logger

app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get('X-Response-Time');
  console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// response

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

(示例代码来自 https://koa.bootcss.com/

ctx
next

1,贯穿整个生命周期的一个对象,包含request对象,response对象。与请求和响应的一切活动皆围绕ctx对象进行

2,它可以被任意删减修改属性,正如JavaScript的灵活性,同时也具备风险,所以中间件是危险的,引入一个中间件的同时可能会影响下游中间件

1,泛指下游所有中间件,执行可对下游所有中间件进行迭代

2,默认下游操作是一个异步的过程

async function

简单来讲,使用async function,我们可以使用同步的代码结构,编写异步的逻辑

异步的JavaScript代码结构衍化

asyncCall1(res1 => {
  asyncCall2(res2 => {
    asyncCall3(res3 => {
      /// ...
    })
  })
})
asyncCall1().then(res1 => {
  return asyncCall2()
}).then(res2 => {
  return asyncCall3()
}).then(res3 => {
  /// ...
})
async function asyncCalls() {
  const res1 = await asyncCall1()
  const res2 = await asyncCall2()
  const res3 = await asyncCall3()
  // ...
}

phase 1: callback

phase 2: promise

phase 3: async function

平台前端nodejs主要模块及作用

node.js擅长领域

  1. 前端自动化

    1. webpack

    2. gulp

    3. ...

  2. headless browser

  3. BFF

  4. ...

前端自动化

  1. 打包构建

    1. webpack,gulp

  2. 预处理,后处理

    1. ts,scss,postcss

  3. 模版生成

  4. ...

headless browser

  1. 截图服务

  2. 爬虫

  3. 搜索引擎优化

  4. ...

例 2.0 headless browser 截图

const browser = await puppeteer.launch();

const page = await browser.newPage();
await page.goto('https://example.com');

console.log(await page.content());
await page.screenshot({path: 'screenshot.png'});

await browser.close();

(示例代码来自https://try-puppeteer.appspot.com

BFF

  1. 微服务发展的产物,并不局限于nodejs

  2. nodejs(前端)来做BFF的优势

    1. api本是面向ui,服务端来做会有重复的理解成本和沟通成本

    2. http request方和response方统一负责,降低理解沟通成本

    3. 服务端渲染

    4. ...

BFF在某条某部门

  1. BFF层生成框架
    1. ​基于 protobuf 定义api接口,并且自动生成代码架子
    2. protobuf 生成成 typescript Interface定义
  2. go service层生成框架
    1. ​自动生成代码架子
    2. 使用 thrift 协议
  3. 基于k8s的服务调度
    1. ​instance无状态
    2. 运维全部自动化

thx

nodejs

By shaomingquan

nodejs

  • 627