深入浅出koa洋葱模型

关于洋葱模型很多人都理解,并且绝大多数人都知道要想保证洋葱模型必须要使用async 和await 

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

答案是肯定的,以至于现在很多人只要是写中间件必用async 和 await 那么你是否知道它的运行机制和底层原理的 一个合格的开发人员是不是要做到知其然还要知其所以然呢?

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

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:5555

上述代码中fun2执行时后续执行栈中并没有同步任务 

因此Promise中的异步任何将会执行并且抛出一个异常

async fun2会返回一个Promise并且默认执行reject

async fun1会处于出栈状态并且接受到fun2的Promise

因为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强行压栈

 

posted on 2020-05-21 11:32  素心~  阅读(1340)  评论(0编辑  收藏  举报

导航