自己实现一个async函数

本文探讨了如何自己实现一个async函数,通过这种方式来理解es6 async函数的语法糖特性。代码示例展示了如何将promise的链式调用转换为同步形式,提高代码可读性。测试用例包括了-delegation和递归委托的情况,演示了async函数内部的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自己实现一个async函数

async函数能将promise链式调用的写法改成同步的写法。让代码变得更简洁易懂。es6中的async是语法糖,我们自己可以实现类似async函数的功能。

下面是myAsync的代码:

/**
 *
 * @param {Function} gen 一个generator函数
 */
function myAsync(gen) {
  var args = [].slice.call(arguments, 1), it;
  it = gen.apply(this, args);

  function fn(nextVal) {
    if (!nextVal.done) {
      return Promise.resolve(nextVal.value).then(
        function (v) {
          return fn(it.next(v));
        },
        function (err) {
          return fn(it.throw(err));
        }
      )
    } else {
      return Promise.resolve(nextVal.value)
    };
  }

  return fn(it.next());
}

下面是测试用例:

/**
 * 下面这个函数模拟异步请求返回一个promise
 * @param {String} resource 资源的路径地址
 * @param {Boolean} success true表示能请求成功,false表示请求失败
 * @returns {Promise}
 */
function request(resource, success) {
  var randomTime = Math.ceil(Math.random() * 10) * 100;
  console.log(resource + ' spend:', randomTime);
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (success) {
        resolve(resource);
      } else {
        reject(resource);
      }
    }, randomTime);
  })
}

/**
 * 下面的generator函数写平常的业务逻辑。
 * 如果下面的console.time得出来的时间与三条请求的时间之和
 * 大约相等,说明main函数内的代码以类似同步的方式执行。
 */
function* main(returnValue) {
  console.time('count');
  var rq1 = yield request('/request1', true);
  var rq2 = yield request('/request2', true);

  try {
    var rq3 = yield request('/request3', false);
  } catch (e) {
    console.log('this is an error', e);
  }

  var rq4 = yield request('/request4', true);

  console.timeEnd('count');
  console.log(returnValue);
  return returnValue;
}

myAsync(main, 'hello my async');

可以看出main函数内部写法和es6的async函数很相似,相当于将await替换成yield

下面是generator中出现yield-delegation中的测试。

//这里依赖上面的 myAsync 和 request 方法

function* subTask() {
  var rq2 = yield request('/request2', true);
  var rq3 = yield request('/request3', true);
  var rq4 = yield request('/request4', false);// promise rejected
  var rq5 = yield request('/request5', true);
}

function* main(returnValue) {
  console.time('count');
  var rq1 = yield request('/request1', true);

  try {
    yield* subTask();
  } catch (err) {
    console.log('catch error from subTask.', err)
  }

  var rq5 = yield request('/request6', true);

  console.timeEnd('count');
  return returnValue;
}

myAsync(main, 'hello my async');

上面的main方法内yield*语句将yield委托给了subTask方法,并且subTask内req4的promise被rejected。此时的myAsync还能正常运行。

下面是generator中出现递归委托的测试。

//这里依赖上面的 myAsync 和 request 方法

function* subTask(num) {
  if (num < 3) {
    yield* subTask(num + 1);
  }

  yield request('/request' + num, true);
}

function* main(returnValue) {
  console.time('count');
  var rq1 = yield request('/request1', true);

  yield* subTask(2);

  var rq4 = yield request('/request4', true);

  console.timeEnd('count');
  return returnValue;
}

myAsync(main, 'hello my async');
<think>好的,我现在需要解决用户的问题:“如何判断一个函数是否带async”。用户之前询问过JavaScript中判断变量类型的方法,现在可能是在开发中遇到了需要检测异步函数的情况。首先,我得回忆一下JavaScript中async函数的特点。 Async函数在ES2017引入,它们返回Promise对象,并且在语法上使用async关键字声明。用户想知道如何检测一个函数是否是async函数。常见的类型检查方法有typeof、instanceof,但可能不适用,因为async函数本质上还是函数,typeof会返回"function"。因此,可能需要其他方法。 首先想到的是检查函数的构造函数名称。普通函数的构造函数是Function,而async函数可能有所不同?或者通过函数的原型链?不过可能需要更深入的分析。 接下来,想到可以使用函数的toString方法。因为函数的toString会返回其源代码字符串,如果包含async关键字,可能可以检测到。比如,将一个函数转换为字符串,然后检查是否以"async"开头。例如,async function foo() {}的toString结果可能是"async function foo() { ... }"。不过这可能依赖于具体实现,不同环境或压缩工具可能会影响结果,比如代码被压缩后是否保留async关键字? 另外,可能还有其他方法,比如检查函数的原型或属性。比如,async函数是否具有特定的属性或Symbol?需要查阅相关文档。 然后,想到使用Object.prototype.toString.call(func),之前用户的问题中提到了这种方法,可以返回内部[[Class]]。对于普通函数,返回"[object Function]",而async函数是否不同?可能需要测试一下。例如,测试async函数的Object.prototype.toString.call结果,但可能仍然返回"[object Function]",因为async函数函数的一种。 另外,可以检查函数的constructor.name属性。普通函数的constructor.name是"Function",而async函数的可能不同?或者可能同样是"AsyncFunction"。需要验证这一点。例如,创建一个async函数,然后查看其构造函数名称。在Chrome中测试async函数,可能得到AsyncFunction构造函数,因此func.constructor.name === "AsyncFunction"。这可能是一个可行的方法,但需要考虑浏览器兼容性,因为AsyncFunction可能在某些环境中不可用,或者被修改。 另外,可能需要考虑如何获取AsyncFunction的构造函数。比如,通过异步函数的构造函数来比较。例如: const AsyncFunction = (async () => {}).constructor; 然后,判断一个函数是否是AsyncFunction的实例,即func instanceof AsyncFunction。这可能是一个可靠的方法,但需要确保获取到正确的AsyncFunction引用,并且在不同环境(如Node.js、不同浏览器)中是否一致。 需要注意的是,用户可能想要区分普通函数async函数,即使它们的行为相似(都返回Promise)。但有时候可能需要明确知道函数是否被声明为async,因为async函数内部可以使用await,并且错误处理方式不同。 总结可能的检测方法: 1. 使用func.constructor.name === "AsyncFunction"。 2. 使用func instanceof AsyncFunction(需要获取AsyncFunction的引用)。 3. 检查函数字符串是否包含"async"关键字(可能不可靠)。 4. 使用Object.prototype.toString.call(func)是否返回特定值。 需要逐一验证这些方法的有效性。例如,测试: async function test() {} console.log(test.constructor.name); // 应该输"AsyncFunction" console.log(test instanceof AsyncFunction); // 需要AsyncFunction的定义,可能通过(async function() {}).constructor获取。 同时,需要注意,如果用户通过eval或其他方式动态创建函数,或者使用转译工具如Babel,可能会影响检测结果。此外,如果函数的constructor被修改,也可能导致检测失败。 另外,考虑箭头函数形式的async函数,比如const fn = async () => {},是否同样适用上述方法。 可能还需要处理边缘情况,比如普通函数返回Promise,但不是async函数的情况。例如: function fn() { return new Promise(...); } 此时,检测方法应正确识别它不是async函数。 最后,需要给用户可靠的方法,并指各方法的优缺点,以及兼容性情况。可能推荐使用constructor.name或instanceof AsyncFunction的方法,同时指潜在问题,如旧浏览器不支持,或者constructor被修改的风险。</think>在 JavaScript 中判断一个函数是否是 `async` 函数,可以通过以下方法实现,每种方法都有其底层原理和适用场景: --- ### **一、通过构造函数名称检测** #### 原理 `async` 函数本质是 `AsyncFunction` 构造函数的实例,与普通函数的 `Function` 构造函数不同: ```javascript const AsyncFunction = (async () => {}).constructor; ``` #### 实现 ```javascript function isAsyncFunction(fn) { return fn.constructor.name === "AsyncFunction"; } // 测试 async function demo() {} console.log(isAsyncFunction(demo)); // true console.log(isAsyncFunction(() => {})); // false ``` #### 底层机制 - V8 引擎会为 `async` 函数创建特殊的 `AsyncFunction` 构造函数 - 普通函数 `function(){}` 的构造函数是 `Function`,而 `async function(){}` 的构造函数是 `AsyncFunction` --- ### **二、通过 `instanceof` 运算符** #### 原理 直接比较函数是否是 `AsyncFunction` 的实例: ```javascript const AsyncFunction = (async () => {}).constructor; function isAsyncFunction(fn) { return fn instanceof AsyncFunction; } ``` #### 注意事项 - 需要先通过 `(async function(){}).constructor` 获取 `AsyncFunction` 的引用 - 跨 iframe 或 Realm 的场景可能失效(不同全局环境有不同构造函数) --- ### **三、检查函数字符串表示** #### 原理 `async` 函数在转换为字符串时,会保留 `async` 关键字: ```javascript async function foo() {} console.log(foo.toString()); // "async function foo() {}" ``` #### 实现 ```javascript function isAsyncFunction(fn) { return fn.toString().startsWith("async"); } ``` #### 缺点 - 代码压缩工具可能移除空格或关键字(如 `async function` → `asyncfunction`) - 动态生成的函数可能没有规范化的字符串表示 - 箭头函数语法可能干扰检测(如 `const fn = async () => {}` 的字符串以 `"async () =>"` 开头) --- ### **四、`Object.prototype.toString` 检测** #### 原理 虽然 `async` 函数本质仍是函数,但可通过 `Symbol.toStringTag` 扩展检测: ```javascript Object.prototype.toString.call(async function(){}); // 输 "[object AsyncFunction]" ``` #### 实现 ```javascript function isAsyncFunction(fn) { return Object.prototype.toString.call(fn) === "[object AsyncFunction]"; } ``` #### 兼容性 - 需 ES6+ 环境支持 - 最可靠的标准方法 --- ### **五、行为特征检测** #### 原理 `async` 函数执行后必定返回 Promise: ```javascript async function demo() {} console.log(demo() instanceof Promise); // true ``` #### 缺点 - 普通函数返回 Promise 时会被误判 - 需要实际执行函数,存在副作用风险 --- ### **对比总结** | 方法 | 可靠性 | 兼容性 | 风险点 | |-----------------------|--------|--------------|----------------------------| | 构造函数名称检测 | 高 | ES2017+ | 构造函数可能被修改 | | `instanceof` 检测 | 高 | ES2017+ | 跨 Realm 失效 | | 字符串检测 | 低 | 所有环境 | 代码压缩/格式变化导致失效 | | `Object.toString` | 高 | ES6+ | 需支持 `Symbol.toStringTag`| | 行为特征检测 | 低 | 所有环境 | 误判普通返回 Promise 的函数| --- ### **底层实现机制** 1. **`AsyncFunction` 构造函数** JavaScript 引擎在解析 `async` 函数时,会通过内部方法 `CreateAsyncFunction` 创建对象,其原型链为: $$ \text{AsyncFunction} \rightarrow \text{Function} \rightarrow \text{Object} $$ 2. **协程与 Promise 包装** `async` 函数在底层会被编译为生成器函数 + 自动 Promise 包装(类似 `function*` + `co` 库的效果),通过事件循环机制管理 `await` 的暂停与恢复。 --- ### **最佳实践建议** 1. **推荐方案** ```javascript const isAsyncFunction = (fn) => Object.prototype.toString.call(fn) === "[object AsyncFunction]"; ``` - 标准方法,兼容现代浏览器和 Node.js - 不依赖构造函数引用 2. **备选方案** ```javascript const AsyncFunction = (async () => {}).constructor; const isAsyncFunction = (fn) => fn instanceof AsyncFunction; ``` - 适用于可控环境(确保 `AsyncFunction` 正确获取) 3. **避免使用字符串检测** 代码压缩和跨引擎差异可能导致误判。 --- ### **特殊场景处理** 1. **跨 Realm 检测** 使用 `Function.prototype.toString` 提取构造函数名称: ```javascript function isAsyncFunction(fn) { return /^async/.test(Function.prototype.toString.call(fn)); } ``` 2. **兼容旧环境** 通过 `try-catch` 执行检测: ```javascript let isAsyncSupported; try { new Function("async function test(){}"); isAsyncSupported = true; } catch { isAsyncSupported = false; } ``` --- ### **延伸扩展** - **箭头函数async** `async` 箭头函数同样会被识别为 `AsyncFunction`: ```javascript const fn = async () => {}; console.log(Object.prototype.toString.call(fn)); // "[object AsyncFunction]" ``` - **动态生成 async 函数** 通过 `AsyncFunction` 构造函数创建: ```javascript const AsyncFunction = (async () => {}).constructor; const dynamicAsyncFn = new AsyncFunction('return await 42;'); ``` 通过上述方法,可以准确判断函数是否是 `async` 函数,根据具体场景选择最适合的方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值