在 JavaScript 中,异步操作是非常常见的,例如网络请求、定时器和事件处理等。在回调函数被广泛应用之后,ES6 推出了 Promise,进一步改善了异步编程的体验。而 Promise 是如何实现异步操作的呢?它与 JavaScript 的微任务有什么关联呢?在本文中,我们将会详细探讨这些问题。
什么是异步操作
在 JavaScript 中,异步操作指的是一些操作不会立即得到结果,而会在后续才会完成,并且该操作会先返回一个 Promise 对象,以便我们在得到结果的时候进行处理。例如:
fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error));
在上述代码中,我们使用 fetch 函数来获取数据并返回一个 Promise 对象。当数据得到响应后,我们将其转化为 JSON 格式并输出到控制台。如果在获取数据的过程中出现了错误,则会输出相关的错误信息。
Promise 的实现原理
Promise 对象是如何实现异步操作的呢?在执行异步操作时,用于处理异步操作的代码被称为微任务(microtask)。微任务是通过一个叫做 Promise 内部作业队列(job queue)的数据结构来进行存储的。当使用 Promise7 来完成异步操作时,就有可能向作业队列添加一个微任务。
代码片段如下:
const myPromise = new Promise(resolve => resolve()); myPromise.then(() => console.log('Promise resolved!')); console.log('Current sync Execution context');
对应的控制台输出:
Current sync Execution context Promise resolved!
在上述代码中,我们首先创建了一个 Promise 对象,并在 .then()
方法中输出了一个字符串。接着,我们在同步执行上下文中输出一段文字。在执行的过程中,推入微任务队列,异步执行回调,所以会先输出同步执行的内容而后输出 Promise 中的结果。
Promise 的状态和生命周期
Promise 的生命周期包括三种状态:
- pending (待定): 初始状态或者执行中的状态。
- fulfilled (已完成):表示Promise成功完成执行操作。
- rejected (已失败):表示Promise未能完成执行操作。
在 Promise 执行完成后,不管是成功的完成还是失败的结束,都将产生一个值。而这个值也被称为 Promise 的结果。如果 Promise 执行成功,将会传递返回值给 then
方法的第一个参数;如果 Promise 执行失败,将会传递错误信息给 catch
方法的回调函数或者是 then
方法的第二个参数。
代码示例如下:
-- -------------------- ---- ------- ----- --------- - --- ----------------- ------- -- - ------------- -- - -- -------------- - ---- - ---------------- ------------ - ---- - --------------- ------------ - -- ------ --- --------- --------- -- ----------------- ---------- -- --------------------
在上述代码中,我们创建了一个 Promise 对象并在 1s 后返回一个随机结果。当结果小于 0.5 的时候,我们将 Promise 转化为 resolved 状态,反之则将其转化为 rejected 状态,并将结果传递给 then
方法或 catch
方法的回调函数。
微任务与宏任务
在前面的文章中,我们简单地介绍了 Promise 的内部作业队列。而这个作业队列是通过 JavaScript 中的两个队列实现的:微任务队列和宏任务队列。
在 JavaScript 中,存在两种类型的任务:宏任务和微任务。当前的回调函数始终位于微任务队列中,而在浏览器内部,微任务队列位于事件循环的过程的“执行被选择的微任务”步骤中。换句话说,只有当前执行的宏任务完成后,传递给当前代码的所在队列的所有微任务才会执行。
下面是一个例子展示了 Promise 与微任务的关系:
new Promise((resolve) => { console.log('Promise Executor'); resolve(); }) .then(() => console.log('Promise Resolved')); console.log('Current Execution context');
在此代码中,我们创建一个 Promise 对象,并在 .then()
方法中输出一个字符串。这个 Promise 对象处于待定状态时,我们打印了一些文本并将其立即解析。接着在同步执行上下文中输出一段文字。在执行回调时,会把这个回调加入到当前上下文的微任务队列中,而在整个 EventLoop 执行完成之前,它都不会执行。因此,输出的先后顺序分别是:
// Output: Promise Executor Current Execution context Promise Resolved
Promise 和 async/await 的关系
在 ES8 中引入了 async/await
语法,从而进一步简化了异步编程。在这个模型中,我们使用 async/await
语法来避免回调的嵌套,同时可以直接监视 Promise 的结果。
下面是一个展示 Promise 和 async/await 的关系的例子:
-- -------------------- ---- ------- -------- ----------- - ------ --- --------------- -- - ------------- -- ------------- --------- ------ -- - ----- -------- ------------- - --------------------- ----- ------ - ----- ------------ -------------------- ------------------- - --------------
在此代码片段中,我们使用异步 asyncTask
函数获得了一个 Promise 对象。使用 async/await
语法等待异步任务的结果。在函数执行期间,console.log()
输出“Start”和“End”文本,同时在异步任务完成后打印任务结果。
在 async/await 中,await 操作符会将当前代码挂起,直到所等待的 promise 被解析。因为它自身是一个异步操作,所以 await 后面的代码被添加到微任务队列中并在下一个微任务中执行。
结论
在本文中,我们详细探讨了 JavaScript 中的微任务和 ES6 的 Promise,这也是现代 JavaScript 异步编程的核心。我们介绍了如何使用 Promise 以及如何在 async/await 中使用它,同时也深入解释了微任务队列和 EventLoop 的概念,进一步提高了 JavaScript 操作异步操作的水平。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/67329ace0bc820c5823df2d4