深刻浅出koa洋葱模型

关于洋葱模型不少人都理解,而且绝大多数人都知道要想保证洋葱模型必需要使用async 和await node

那么问题来了async和 await 是 用来解决异步编程的,那么当咱们调用的下一个中间件不存在异步的时候,是否还须要使用async和 await编程

答案是确定的,以致于如今不少人只要是写中间件必用async 和 await 那么你是否知道它的运行机制和底层原理的 一个合格的开发人员是否是要作到知其然还要知其因此然呢?koa

咱们就拿最为简单的全局异常处理来举例,在异步编程模型中全局异常处理由于函数执行时压栈与出栈队列的关系,每每有些抽象异步

async function fun() {
  try {
    await fun1();
  } catch (error) {
    console.log(5555);
  }
}

async function fun1() {
  await fun2();
}

async function fun2() {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('error'));
    }, 0);
  });
}

fun();

result:5555async

上述代码中fun2执行时后续执行栈中并无同步任务 异步编程

所以Promise中的异步任何将会执行而且抛出一个异常 函数

async fun2会返回一个Promise而且默认执行rejectoop

async fun1会处于出栈状态而且接受到fun2的Promisespa

由于await的求值特性 因此会执行Promose中的reject再次将错误抛出 而且由fun1包装为Promise返回给fun线程

fun执行Promise中reject抛出错误,由于函数状态队列所有出栈,因此错误将被当即抛出 由try catch捕捉

那么问题来了咱们能够发现fun1中并无异步操做,它惟一作的一件事就是捕捉到了fun2返回Promise 而且再度将这个错误抛出 返回给了fun

那么咱们为何要给fun加await呢?让fun1使用await让它去和fun2压栈很差吗?

其实在这个案例中确实,fun中不使用await异步解决方案,并不影响咱们捕捉到这个错误

但回到问题的自己,咱们说的是要让koa保证洋葱模型,简单修改下代码

async function fun() {
  fun1().catch((err) => {
    console.log(55555);
  });

  console.log('I do it first');
}

async function fun1() {
  await fun2();
}

async function fun2() {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('error'));
    }, 0);
  });
}

fun();

result:I do it first

result:5555

前文已经说过了async会将抛出的错误用一个Promise进行包装,而await的求值特性会执行reject从而抛出错误

那么修改后的代码咱们再也不使用await,就须要手动catch这个错误了,但这并不影响它自己捕捉这个错误不是吗?

但若是你是一个koa玩家,这个时候大几率必定明白了,是的

洋葱模型要保证执行顺序,在fun1,fun2所有执行完毕后才能够调用I do it first

可是很明显结果并非,这就是为何即使下一个中间件不存在异步编程咱们也要使用async的缘由

至于缘由,是由于node毕竟是基于V8引擎,而其自己单线程的特性,在遇到await时会对其所在的函数进行压栈先去执行执行栈中的同步任务

这也是应有之意吧,因此在咱们给fun加上await以后,咱们就强制要求它进行压栈,必须等fun2 fun1处理完毕后再行出栈

那么也就保证了洋葱模型,至于node的运行原理,event loop 事件队列这一些,我相信是一个JS玩家的基本素质了吧

好的那么再加一层吧,看看能不能捕捉到,是的捕捉到了依然是层层执行,抛出错误,包装为Promise,是否是颇有魅力呢?可是问题依然不能保证洋葱模型

 

async function fun() {
  add().catch((err) => {
    console.log(55555);
  });

  console.log('I do it first');
}

async function add() {
  await fun1();
}

async function fun1() {
  await fun2();
}

async function fun2() {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('error'));
    }, 0);
  });
}

fun();

咱们看到代码中除了fun2中存在异步编程其余都没有,可是必需要使用async和await才能保证错误层层递进,固然了函数出栈时自己是不受错误影响的

但有一个问题请不要忽略,那就是若是你不对当前函数进行压栈,那么当它执行到fun2时发现是异步编程对其压栈

那么fun1和add不进行压栈那么它们就会瞬间执行完毕,从而你在对fun进行压栈它依然捕捉不到错误

由于当add执行完毕时,fun就已经出栈了,可是add并无捕捉到错误,从而也就不可能抛出错误了好的再次修改代码举下例

async function fun() {
  try {
    await add();
  } catch (error) {
    console.log(2222);
  }

  console.log('I do it first');
}

async function add() {
  fun1();
}

async function fun1() {
  await fun2();
}

async function fun2() {
  await new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('error'));
    }, 0);
  });
}

fun();

这个时候add并无使用await进行压栈,那么咱们来推演一下执行过程,fun调用add,add调用fun1 

fun1调用fun2,当执行栈到这里时,发现存在await异步编程,对fun2进行压栈,而且返回一个pending状态的Promise

此时fun1由于fun2压栈也会进行压栈,而且再度抛出pending状态的Promise,这个时候add拿到了fun1抛出的Promise

若是这个时候对add拿到的Promise就行catch那么将会fun2出栈时会执行reject抛出错误,fun1所以也就能拿到错误而且执行

这个时候add天然可以拦截错误,但问题是咱们须要让fun拦截错误,因此又回到了问题自己必须给使用await对add强行压栈

相关文章
相关标签/搜索