为什么用forEach进行异步操作,await了个寂寞?

195 阅读2分钟

「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

问题

在做根据Swagger文档进行代码生成的过程中,因为服务端规范采用@ApiModal对数据结构进行了注解,导致这一行为覆盖了原本swagger的命名生成规则,而用户定义的注解是不规范的,存在中文注解的概率基本是100%,但生成的ts代码类型命名又不能是中文,因此需要对可能存在的中文命名进行翻译,所以就产生了今天这个话题:用forEach进行异步操作,await了个寂寞.

番外话:
翻译采用的是谷歌翻译:@vitalets/google-translate-api

下面上伪代码(忽略代码签证健壮性问题,随手写,不是重点):

const translate = require('@vitalets/google-translate-api')

/** 递归翻译符合Json Scheme协议数据类型为'object'和'array'的title字段 */
const translateZhToEn = async ()=>{
  return async function loopTranslate(data){
    const {properties, title, type} = data || {}
    if(type==='array' || type === 'object'){
      data['title'] =  await translate(title, { to: 'en', tld: 'cn' })
      Object.keys(properties).forEach(async (key)=>{
        const {properties, title, type} = properties[key]
        if(type==='array' || type === 'object'){
          await loopTranslate(properties[key])
        }
      })
    }
  }
}
// 符合Json Scheme的数据
const data = {}
translateZhToEn()(data)

按照上面理想的例子,按照脑子180码快速旋转运算,自信是没问题的。但运行的时候,傻眼了,并没有按照正常的输出,按逻辑是:下面数据结构的掘金用户用户标签需要全部被翻译成中文,但实际上并没有!那就得找问题了!

{
  title:'掘金用户',
  type: 'object',
  properties:{
    id: {type: 'string'},
    user_name: {type: 'string'},
    label: {
      title: '用户标签',
      type: 'object',
      properties: {
        label_id: {type: 'string'},
        label_name: {type: 'string'}
      }
    }
  }
}

解决问题

问题很明显,递归异步并没有按我们想象中走,打个断点调试一下,很快就能确定是forEach闹了鬼,本着负责任的代码,去看了forEachpolyfill,上代码:

if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(callback, thisArg) {

    var T, k;

    if (this == null) {
      throw new TypeError(' this is null or not defined');
    }

    var O = Object(this);
    var len = O.length >>> 0;
    
    if (typeof callback !== "function") {
      throw new TypeError(callback + ' is not a function');
    }
    
    if (arguments.length > 1) {
      T = thisArg;
    }

    k = 0;
    // 重点代码块
    while (k < len) {
      var kValue;
      if (k in O) {
        kValue = O[k];
        callback.call(T, kValue, k, O);
      }
      k++;
    }
  };
}

看完是不是一目了然,while 循环内部只是执行了callback方法,也就是forEach的回调方法体,对于是异步函数,它并没有做任何判断,也并没有做任何处理,因此这里使用async/await对于结果返回值顺序,它是不保证的。换句话说,forEach循环异步在走,但也不妨碍主程序往下走,因此当主程序在异步结束前去操作对应的值,此时的值并不是都已经被forEach处理完成的。

扩展下,就算你在forEach的回调函数使用了break,也并不能结束整个数据的遍历过程。那针对这类问题如何解决呢?

  1. 直接采用for循环
  2. 采用for ... of