Nodejs设计模式 - 阅读笔记

avatarplhDigital nomad

回调模式

首先,函数必须是可被赋值的变量,这个时候,才能作为参数被传递. 其次,函数必须是闭包,才能实现异步回调. 为什么是闭包呢?因为在JavaScript中,函数就是闭包,而异步函数可以准确获取上下文中的变量. 要知道,异步函数是异步的,也就是说,当前js主任务已经全部执行完毕,一切变量都会被销毁的,而异步函数是因为闭包的关系,引用了外部变量造成,外部变量在js执行完毕后,变量因为引用次数不为零.不能被垃圾回收机制回收. 进而异步函数可以继续从上下文中准确的调用内存存址中的变量.

const add = (a,b) => a+b;
function middleware(call){
  console.log('before');
  call(3,4)
  console.log('after');
}
middleware((a,b)=>{
  console.log('result is ', add(a,b))
})

因为是同步的执行关系,执行顺序如下,

before
result is  7
after

下面请看异步的代码

const add = (a,b) => a+b;
function middleware(call){
  console.log('before');
  call(3,4)
  console.log('after');
}
middleware((a,b)=>{
  setTimeout(() => {
    console.log('result is ', add(a,b))
  }, 100);
})

异步代码执行结果如下

before
after
result is  7

原因是因为,当异步代码执行完毕之后,会把控制权交回给事件模型,因此,重新在任务队列中插入执行的代码,任务队列又在函数调用栈中插入待执行代码. JavaScript的灵活之处再次体现出来,闭包的原因,上下文被引用的变量并没有消失,因此,异步函数可以在任何地方,任何时间,调用函数.也不需要维护上下文中的变量.

不可预测的函数

下面的函数读取代码,之前读取过的代码会被缓存在内存中,,但是同时也会带来一个问题,那就是无法判断函数是同步执行还是异步. 因此约定俗称,要么全部同步代码,要么全部异步执行的代码,

const fs = require('fs');
const cache = {};

function inconsistenRead(file,call){
  if(cache[file]){
    call(cache[file]);
  } else {
    fs.readFile('index.html','utf-8',((err,data)=>{
      cache[file] = data;
      call(data);
    }))
  }
}

function createFileReader(file){
  const listeners = [];
  inconsistenRead(file,val=>{
    listeners.forEach(listener=>listener(val))
  });
  return {
    onDataReady: listener => listeners.push(listener)
  }
}

const reader1 = createFileReader("index.html");

reader1.onDataReady(data=>{
  console.log(data);
  const reader2 = createFileReader('index.html');
  reader2.onDataReady(data=>{
    console.log('data');
  })
})

当解决方案是: 全部同步

建议在程序启动时选择同步阻塞代码,或者在不影响程序处理并发请求的时候选择阻塞代码,其他都选择异步代码.

const fs = require('fs');
const cache = {};

function inconsistenRead(file) {
  if (cache[file]) {
    return (cache[file])
  } else {
    cache[file] = fs.readFileSync(file, 'utf-8');
    return cache[file];
  }
};

function createFileReader(file) {
  const listeners = [];
  const res = inconsistenRead(file);
  listeners.forEach(listener => listener(res));
  return {
    onDataReady: listener => {
      listeners.push(listener);
      console.log(listeners);
      listener(res);
    }
  }
};

const reader1 = createFileReader("index.html");

reader1.onDataReady(data => {
  console.log(data);
  console.table('======================================');
  const reader2 = createFileReader('index.html');
  reader2.onDataReady(data => {
    console.log(data);
  })
});

当解决方案是: 全部异步

将同步代码放在process.nextTick()中执行,当然你也可以用setImmediate().也就是下一个事件循环周期,.修改inconsistenRead代码如下:

const fs = require('fs');
const cache = {};

function inconsistenRead(file,call){
  if(cache[file]){
    process.nextTick(
      ()=>call(cache[file])
    )
  } else {
    fs.readFile(file,'utf-8',((err,data)=>{
      cache[file] = data;
      call(data);
    }))
  }
}

function createFileReader(file){
  const listeners = [];
  inconsistenRead(file,val=>{
    listeners.forEach(listener=>listener(val))
  });
  return {
    onDataReady: listener => listeners.push(listener)
  }
}

const reader1 = createFileReader("index.html");

reader1.onDataReady(data=>{
  console.log(data);
  console.table('======================================');
  const reader2 = createFileReader('index.html');
  reader2.onDataReady(data=>{
    console.log(data);
  })
})

process.nextTick();

触发于任何其他I/O事件被调用之前

setImmediate();

触发于任何其他I/O事件被调用之后

回调函数置尾 ,, 错误优先暴露,,,传播错误

下面查看文件读取函数的API,同样是基于这样的设计原则. 回调置尾是为了提升函数可读性. 错误暴露优先,保证每次回调都会检查,另一个是err始终为ERROR类型.

fs.readFile(fileName, [options], (err,data)=>{});

下面便是读取文件的代码,遵从将回调函数的错误作为第一个参数进行传递的原则,并且优先暴露错误. 存在2种错误, 1.读取文件错误 2.读取的文件不是正确的json格式,发生解析错误.

const fs = require('fs');

function readJSON(file, call){
  fs.readFile(file,'utf-8',(err,data)=>{
    let parsed;
    if(err){
      return call(err);
    }
    try {
      parsed = JSON.parse(data);
    } catch (err) {
      return call(err);
    };
    call(null, parsed);  // 遵从回调函数第一个参数为错误参数的原则.
  })
}

readJSON('s.json',(err,data)=>{
  if(err){
    console.log('err:',err);
  }
  console.log(data);  
})
console.log('ttt');

未捕获异常

如果把try...catch代码删了会怎样.

const fs = require('fs');
function readJSON(file, call){
  fs.readFile(file,'utf-8',(err,data)=>{
    let parsed;
    if(err){
      return call(err);
    }
    // try {
    //   parsed = JSON.parse(data);
    // } catch (err) {
    //   return call(err);
    // };
    call(null, parsed);  // 遵从回调函数第一个参数为错误参数的原则.
  })
}
readJSON('s.json',(err,data)=>{
  if(err){
    console.log('err:',err);
  }
  console.log(123);
  console.log(data);  
})
console.log('sdf');

于是乎,报错如下,并且代码阻塞了..

SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at fs.readFile (/Users/pengliheng/www/adb/index.js:10:21)
    at FSReqWrap.readFileAfterClose [as oncomplete] (internal/fs/read_file_context.js:53:3)

这是一段无法捕获的异步代码错误

try {
  readJSON('s.json',(err,data)=>{
    if(err){
      console.log('err:',err);
    }
    console.log(data);  
  })
} catch (err) {
  console.log(err);
}