0
点赞
收藏
分享

微信扫一扫

js 期约与异步函数

等待会抛出错误的同步操作,会返回拒绝的期约:

```async function foo() {

console.log(1);

await (() => { throw 3; })();

}

// 给返回的期约添加一个拒绝处理程序

foo().catch(console.log);

console.log(2);

// 1

// 2

// 3

```

如前面的例子所示,单独的 Promise.reject()不会被异步函数捕获,而会抛出未捕获错误。不过,对拒绝的期约使用 await 则会释放(unwrap)错误值(将拒绝期约返回):

```async function foo() {

console.log(1);

await Promise.reject(3);

console.log(4); // 这行代码不会执行

}

// 给返回的期约添加一个拒绝处理程序

foo().catch(console.log);

console.log(2);

// 1

// 2

// 3

```

await 的限制

await 关键字必须在异步函数中使用,不能在顶级上下文如<script>标签或模块中使用。不过,定义并立即调用异步函数是没问题的。下面两段代码实际是相同的:

```async function foo() {

console.log(await Promise.resolve(3));

}

foo();

// 3

// 立即调用的异步函数表达式

(async function() {

console.log(await Promise.resolve(3));

})();

// 3

```

此外,异步函数的特质不会扩展到嵌套函数。因此,await 关键字也只能直接出现在异步函数的定义中。在同步函数内部使用 await 会抛出 SyntaxError。

下面展示了一些会出错的例子:

```// 不允许:await 出现在了箭头函数中

function foo() {

const syncFn = () => {

return await Promise.resolve('foo');

};

console.log(sy

}

// 不允许:await 出现在了同步函数声明中

function bar() {

function syncFn() {

return await Promise.resolve('bar');

}

console.log(syncFn());

}

// 不允许:await 出现在了同步函数表达式中

function baz() {

const syncFn = function() {

return await Promise.resolve('baz');

};

console.log(syncFn());

}

// 不允许:IIFE 使用同步函数表达式或箭头函数

function qux() {

(function () { console.log(await Promise.resolve('qux')); })();

(() => console.log(await Promise.resolve('qux')))();

}

```

停止和恢复执行

使用 await 关键字之后的区别其实比看上去的还要微妙一些。比如,下面的例子中按顺序调用了 3个函数,但它们的输出结果顺序是相反的:

```async function foo() {

console.log(await Promise.resolve('foo'));

}

async function bar() {

console.log(await 'bar');

}

async function baz() {

console.log('baz');

}

foo();

bar();

baz();

// baz

// bar

// foo

```

async/await 中真正起作用的是 await。async 关键字,无论从哪方面来看,都不过是一个标识符。

毕竟,异步函数如果不包含 await 关键字,其执行基本上跟普通函数没有什么区别:

```async function foo() {

console.log(2);

}

console.log(1);

foo();

console.log(3);

// 1

// 2

// 3

```

要完全理解 await 关键字,必须知道它并非只是等待一个值可用那么简单。JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。

因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。下面的例子演示了这一点:

```async function foo() {

console.log(2);

await null;

console.log(4);

}

console.log(1);

foo();

console.log(3);

// 1

// 2

// 3

// 4

```

控制台中输出结果的顺序很好地解释了运行时的工作过程:

(1) 打印 1;

(2) 调用异步函数 foo();

(3)(在 foo()中)打印 2;

(4)(在 foo()中)await 关键字暂停执行,为立即可用的值 null 向消息队列中添加一个任务;

(5) foo()退出;

(6) 打印 3;

(7) 同步线程的代码执行完毕;

(8) JavaScript 运行时从消息队列中取出任务,恢复异步函数执行;

(9)(在 foo()中)恢复执行,await 取得 null 值(这里并没有使用);

(10)(在 foo()中)打印 4;

(11) foo()返回。

如果 await 后面是一个期约,则问题会稍微复杂一些。此时,为了执行异步函数,实际上会有两个

任务被添加到消息队列并被异步求值。下面的例子虽然看起来很反直觉,但它演示了真正的执行顺序:①

```async function foo() {

console.log(2);

console.log(await Promise.resolve(8));

console.log(9);

}

async function bar() {

```

① TC39 对 await 后面是期约的情况如何处理做过一次修改。修改后,本例中的 Promise.resolve(8)只会生成一个异步任务。因此在新版浏览器中,这个示例的输出结果为 123458967。实际开发中,对于并行的异步操作我们通常更关注结果,而不依赖执行顺序。

console.log(4);

console.log(await 6);

console.log(7);

}

console.log(1);

foo();

console.log(3);

bar();

console.log(5);

// 1

// 2

// 3

// 4

// 5

// 6

// 7

// 8

// 9

```

运行时会像这样执行上面的例子:

(1) 打印 1;

(2) 调用异步函数 foo();

(3)(在 foo()中)打印 2;

(4)(在 foo()中)await 关键字暂停执行,向消息队列中添加一个期约在落定之后执行的任务;

(5) 期约立即落定,把给 await 提供值的任务添加到消息队列;

(6) foo()退出;

(7) 打印 3;

(8) 调用异步函数 bar();

(9)(在 bar()中)打印 4;

(10)(在 bar()中)await 关键字暂停执行,为立即可用的值 6 向消息队列中添加一个任务;

(11) bar()退出;

(12) 打印 5;

(13) 顶级线程执行完毕;

(14) JavaScript 运行时从消息队列中取出解决 await 期约的处理程序,并将解决的值 8 提供给它;

(15) JavaScript 运行时向消息队列中添加一个恢复执行 foo()函数的任务;

(16) JavaScript 运行时从消息队列中取出恢复执行 bar()的任务及值 6;

(17)(在 bar()中)恢复执行,await 取得值 6;

(18)(在 bar()中)打印 6;

(19)(在 bar()中)打印 7;

(20) bar()返回;

(21) 异步任务完成,JavaScript 从消息队列中取出恢复执行 foo()的任务及值 8;

(22)(在 foo()中)打印 8;

(23)(在 foo()中)打印 9;

(24) foo()返回。

举报

相关推荐

0 条评论