Web前端入门第 79 问:JavaScript async & await 的异步任务进化之路
<p>JS 中异步任务随处可见,比如:</p><p>1、用户交互的点击、输入<br>
2、网络请求的 fetch、ajax、WebSocket<br>
3、资源中的图片、脚本加载<br>
4、定时任务 setTimeout、setInterval、动画<br>
5、Web Worker 中的后台任务</p>
<p>以上这些地方都能见到 JS 异步任务使用场景。</p>
<p>不过 JS 的异步任务 <code>使用方法</code> 却经过了多次迭代,多次进化才像一个完全体~~</p>
<h3 id="回调方法">回调方法</h3>
<p>最原始的使用方法,目前也还能在各种钩子函数中见到 <code>回调函数</code> 的身影。</p>
<pre><code class="language-js">function asyncTask(callback) {
console.log('开始执行异步任务');
setTimeout(() => {
console.log('异步任务执行完毕');
callback && callback();
}, 1000);
}
// 传入匿名函数用于回调方法
asyncTask(() => {
console.log('异步任务执行完毕,回调函数执行完毕');
});
</code></pre>
<p>以上 asyncTask 传入的 <code>匿名函数</code> 便是回调方法(也称为回调函数),回调方法将会在等到 <code>setTimeout</code> 执行完毕时执行。</p>
<h3 id="promise">Promise</h3>
<p>在使用回到函数时,容易陷入<code>回调地狱</code>,而 Promise 的出现便是为了解决回调地狱问题。</p>
<p>使用回调函数嵌套太多时,就会有像套娃一样的代码,比如:</p>
<pre><code class="language-js">a(() => {
b(()=> {
c(() => {
d(() => {})
})
})
})
</code></pre>
<p>使用 Promise 优化之后可以是这样:</p>
<pre><code class="language-js">a()
.then(() => {
return b()
}).then(() => {
return c()
}).then(() => {
return d()
})
</code></pre>
<hr>
<p>最开始的 setTimeout 函数使用 Promise 优化之后:</p>
<pre><code class="language-js">function asyncTask() {
return new Promise((resolve) => {
console.log('开始执行异步任务');
setTimeout(() => {
console.log('异步任务执行完毕');
resolve();
}, 1000);
});
}
// Promise 链式调用
asyncTask().then(() => {
console.log('异步任务执行完毕,回调函数执行完毕');
});
</code></pre>
<p>关于 Promise 可参考之前的文章:Web前端入门第 69 问:JavaScript Promise 提供的方法都使用过吗?</p>
<h2 id="async--await">async & await</h2>
<p>使用 Promise 的链式调用确实大大的改善了回调地狱,但还是绕不过代码不太优雅的问题,于是乎 JS 标准定制的那群大佬,就在 ES2017(ES8) 中引入了 <code>async</code> 和 <code>await</code> 关键字,由于优化 JS 中的异步逻辑,使得代码就像同步任务一样。</p>
<p>async & await 仅仅是 Promise 的语法糖,所以它俩基本是与 Promise 深度绑定~~</p>
<p>改写上面的 Promise 示例:</p>
<pre><code class="language-js">(async () => {
function asyncTask() {
return new Promise((resolve) => {
console.log('开始执行异步任务');
setTimeout(() => {
console.log('异步任务执行完毕');
resolve();
}, 1000);
});
}
// await 调用
await asyncTask()
console.log('异步任务执行完毕,回调函数执行完毕');
})()
</code></pre>
<p><code>await</code> 关键字用于等待一个 Promise 任务完成,然后继续执行后续的代码。</p>
<p><strong>注意:</strong>在使用 <code>await</code> 关键字时,必须在外层作用域的函数身上加上 <code>async</code> 关键字,否则会报错。</p>
<h3 id="顶层-await">顶层 await</h3>
<p>在 ES2023 发布后,异步任务又被革命了,在 <strong>ES 模块</strong> 中,允许在顶层作用域使用 await 关键字而不必再套在 async 函数中。</p>
<pre><code class="language-js"><script type="module">
function asyncTask() {
return new Promise((resolve) => {
console.log('开始执行异步任务');
setTimeout(() => {
console.log('异步任务执行完毕');
resolve();
}, 1000);
});
}
// await 调用
await asyncTask()
console.log('异步任务执行完毕,回调函数执行完毕');
</script>
</code></pre>
<p>注意上面的 <strong>type="module"</strong>,表示使用 ES 模块语法,这种语法 Chrome 61 版本开始支持(2017年后)。</p>
<p>如果没有 <strong>type="module"</strong>,表示使用正常的 script 执行脚本,上面的代码会报错:</p>
<pre><code class="language-bash">Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
</code></pre>
<p>表示 await 必须在 async 函数中使用,在顶层使用时候必须放在 ES 模块中。</p>
<h3 id="返回内容">返回内容</h3>
<p><code>await</code> 关键字可以等待一个 Promise resolve 方法返回值:</p>
<pre><code class="language-js">(async () => {
function asyncTask() {
return new Promise((resolve) => {
console.log('开始执行异步任务');
setTimeout(() => {
console.log('异步任务执行完毕');
resolve({ name: '前端路引' });
}, 1000);
});
}
// await 调用
const res = await asyncTask()
console.log('异步任务执行完毕,返回内容:', res);
// 输出 {name: '前端路引'}
})()
</code></pre>
<hr>
<p><strong>生成器函数:</strong> async 规范落地之前,JS 还有过一个 <code>生成器函数</code> 也能用来处理异步任务,不过在实际开发中很少使用,在一些多任务的脚手架里面能看到它的身影,使用方法可参考之前的文章:</p>
<p>Web前端入门第 64 问:JavaScript 几种函数定义方式有什么区别? </p>
<h2 id="写在最后">写在最后</h2>
<p>JS 的任务调度机制让它拥有大量的异步编程,各式各样的使用方式都有必要了解学习,要不然...嘿嘿...大佬写的代码看不懂~~</p>
</div>
<div id="MySignature" role="contentinfo">
<p> </p>
<p style="font-size: 18px;font-weight: bold;">文章首发于微信公众号【<span style="color:rgb(255, 71, 87)">前端路引</span>】,欢迎 <span style="color:#4ec259">微信扫一扫</span> 查看更多文章。</p>
<p>
<img style="max-width: 320px;" src="https://images.cnblogs.com/cnblogs_com/linx/2447020/o_250228035031_%E5%85%AC%E4%BC%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png"/>
</p>
<p>本文来自博客园,作者:前端路引,转载请注明原文链接:https://www.cnblogs.com/linx/p/18989090</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/linx/p/18989090
頁:
[1]