es6 async函数原理及使用方法

本文详细介绍了ES6中async函数的工作原理及其实现方式,并探讨了async函数与Generator函数、Promise之间的关系,以及如何在实际开发中高效地使用async函数。

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

参考网站:

ES6 入门教程

async 原理:

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () { //spawn函数就是自动执行器
    return new Promise(function(resolve, reject) {
          const gen = genF();
          function step(nextF) {
              let next;
              try {
                next = nextF();
              } catch(e) {
                return reject(e);
              }
              if(next.done) {
                return resolve(next.value);
              }
              Promise.resolve(next.value).then(function(v) {
                step(function() { return gen.next(v); });
              }, function(e) {
                step(function() { return gen.throw(e); });
              });
         }
        step(function() { return gen.next(undefined); });
      });
  });
}

 Generator 函数介绍:

async 使用:

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句(await语句整体可以看为一个同步执行区域)。

下面是一个例子。

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
   console.log('111');// 要等stockPrice 执行完才会执行
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});

由于async函数返回的是 Promise 对象,可以作为await命令的参数。

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);

async的执行顺序

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

async的返回

async函数返回一个 Promise 对象。

async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

async的报错

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。

await命令

await命令后面跟随的对象

await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function f() {
  // 等同于
  // return 123;
  return await 123; //等同于return 123
}

f().then(v => console.log(v))
// 123

另一种情况是await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。

class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) { // 因为定义了then方法,await会将其视为Promise处理。
    const startTime = Date.now();
    setTimeout(
      () => resolve(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const actualTime = await new Sleep(1000);
  console.log(actualTime);
})();

await返回

await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

如果await后面的异步操作出错(throw new Error()),那么等同于async函数返回的 Promise 对象被reject

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了

两种解决await中断的方法

1放在try...catch模块中

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))

2await后面的 Promise 对象再跟一个catch方法

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

await注意点

  1. 前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
    async function myFunction() {
      try {
        await somethingThatReturnsAPromise();
      } catch (err) {
        console.log(err);
      }
    }
    
    // 另一种写法
    
    async function myFunction() {
      await somethingThatReturnsAPromise()
      .catch(function (err) {
        console.log(err);
      });
    }
  2. 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发

    let foo = await getFoo();
    let bar = await getBar();
    

    上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

    // 写法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法二
    let fooPromise = getFoo();  // 这里已经触发函数,
    let barPromise = getBar();  //getFoo和getBar都是同时触发,这样就会缩短程序的执行时间
    let foo = await fooPromise;
    let bar = await barPromise;
  3. await命令只能用在async函数之中,如果用在普通函数,就会报错。

    如果将forEach方法的参数改成async函数,也有问题。

    function dbFuc(db) { //这里不需要 async
      let docs = [{}, {}, {}];
    
      // 可能得到错误结果
      docs.forEach(async function (doc) { //原因是三个db.post操作将是并发执行,也就是同时执行,而不是继发执行
        await db.post(doc);
      });
    }
    

    上面代码可能不会正常工作。正确的写法是采用for循环。

    async function dbFuc(db) {
      let docs = [{}, {}, {}];
    
      for (let doc of docs) {
        await db.post(doc);
      }
    }

    如果确实希望多个请求并发执行,可以使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。

    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc)); // 注意map里的函数是return db.post(doc)
    
      let results = await Promise.all(promises);
      console.log(results);
    }
    
    // 或者使用下面的写法
    
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));
    
      let results = [];
      for (let promise of promises) {
        results.push(await promise);
      }
      console.log(results);
    }
  4. async 函数可以用于保留运行堆栈。

    const a = () => {
      b().then(() => c());
    };
    

    上面代码中,函数a内部运行了一个异步任务b()。当b()运行的时候,函数a()不会中断,而是继续执行。等到b()运行结束,可能a()早就运行结束了,b()所在的上下文环境已经消失了。如果b()c()报错,错误堆栈将不包括a()

    现在将这个例子改成async函数。

    const a = async () => {
      await b();
      c();
    };
    

    上面代码中,b()运行的时候,a()是暂停执行,上下文环境都保存着。一旦b()c()报错,错误堆栈将包括a()

async函数对 Generator 函数的改进

(1)内置执行器。

Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();

上面的代码调用了asyncReadFile函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用next方法,或者用co模块,才能真正执行,得到最后结果。

(2)更好的语义。

asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)更广的适用性。

co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

 async 函数与 Promise、Generator 函数的比较。

1与Promise 比较

语义更加明确

2与Generator 比较

有一个任务运行器spawn,它返回一个 Promise 对象,而且必须保证yield语句后面的表达式,必须返回一个 Promise。

扩展部分

asyncIterator异步遍历器

异步遍历器的最大的语法特点,就是调用遍历器的next方法,返回的是一个 Promise 对象。

asyncIterator
  .next()
  .then(
    ({ value, done }) => /* ... */
  );

上面代码中,asyncIterator是一个异步遍历器,调用next方法以后,返回一个 Promise 对象。因此,可以使用then方法指定,这个 Promise 对象的状态变为resolve以后的回调函数。回调函数的参数,则是一个具有valuedone两个属性的对象,这个跟同步遍历器是一样的。

for await...of遍历同步的 Iterator 接口

for...of循环用于遍历同步的 Iterator 接口。新引入的for await...of循环,则是用于遍历异步的 Iterator 接口。

async function f() {
  for await (const x of createAsyncIterable(['a', 'b'])) {
    console.log(x);
  }
}
// a
// b

上面代码中,createAsyncIterable()返回一个拥有异步遍历器接口的对象,for...of循环自动调用这个对象的异步遍历器的next方法,会得到一个 Promise 对象。await用来处理这个 Promise 对象,一旦resolve,就把得到的值(x)传入for...of的循环体。

异步 Generator 函数

就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。

在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。

async function* gen() {
  yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }

上面代码中,gen是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用next方法,返回一个 Promise 对象。

yield* 语句

yield*语句也可以跟一个异步遍历器。

async function* gen1() {
  yield 'a';
  yield 'b';
  return 2;
}

async function* gen2() {
  // result 最终会等于 2
  const result = yield* gen1();
}

上面代码中,gen2函数里面的result变量,最后的值是2

与同步 Generator 函数一样,for await...of循环会展开yield*(自动调用next()

(async function () {
  for await (const x of gen2()) {
    console.log(x);
  }
})();
// a
// b
### ES6 中 `async` 和 `await` 的工作原理 #### 背景介绍 JavaScript 是一种单线程编程语言,这意味着它在同一时间只能执行一项操作。为了处理异步任务而不阻塞主线程,JavaScript 使用事件循环机制来管理回调函数队列。然而,在传统的基于回调或 Promise 的方式中编写异步代码可能会导致嵌套过深(即所谓的“回调地狱”)。为了解决这一问题,ES2017 引入了 `async/await` 关键字作为更简洁和易读的方式来处理异步逻辑。 --- #### 工作原理详解 ##### 1. **`async` 函数** 当一个函数被声明为 `async` 时,该函数会返回一个隐式的 `Promise` 对象[^1]。如果函数内部有显式的 `return` 返回值,则这个值会被自动封装成一个已解决状态的 `Promise`;如果有抛出错误的情况,则会变成拒绝状态的 `Promise`。 ```javascript async function exampleAsync() { return "Hello, world!"; } exampleAsync().then(value => console.log(value)); // 输出: Hello, world! ``` ##### 2. **`await` 表达式** `await` 只能在 `async` 函数内部使用,用于暂停当前函数的执行直到指定的 `Promise` 完成解析并返回其结果[^2]。在此期间,其他非同步的任务可以继续运行而不会受到影响。 - 如果等待的是一个已经完成的 `Promise` 或者是一个普通的值而非真正的 `Promise` 实例,那么 `await` 将立即恢复控制权并将相应数据传递给后续语句。 - 若目标 `Promise` 失败 (`reject`) ,则整个流程也会停止并向上传播异常除非被捕获。 ```javascript // 成功案例 async function fetchUserDataSuccess() { const user = await getUserData(); // 假设这是一个返回 Promise 的 API 请求方法 console.log(user); } // 错误捕获 async function fetchUserDataErrorHandling() { try { const user = await getInvalidUserData(); console.log(user); } catch (error) { console.error('Failed to load data:', error.message); } } ``` ##### 3. **底层实现细节** 实际上,`async/await` 并未改变 JavaScript 执行模型的核心——它们只是语法糖形式简化了对 Promises 的调用过程。每当遇到带有 `await` 的表达式时,编译器会在后台创建一个新的闭包环境保存上下文信息以便稍后再从中断处重新启动程序流[^3]。 --- #### 总结 通过引入 `async/await` 结构,开发者能够写出看起来像顺序执行但实际上却能高效利用资源的异步代码结构。这种方式不仅提高了可维护性和调试便利度,还减少了因复杂嵌套带来的潜在风险。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值