javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS单线程实现异步

JavaScript单线程实现异步的详细代码示例

作者:超级无敌大蟑王

浏览器JavaScript的作用是操作DOM,这就决定了它只能是单线程的,否则会带来很复杂的问题,这篇文章主要介绍了JavaScript单线程实现异步的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

1.浏览器内核:

在讲JavaScript异步之前,先讲一下JavaScript运行环境,因为JavaScript是否能实现异步是通过运行环境机制决定的,我们经常使用的环境就是浏览器环境了,所以我今天主要讲一下在浏览器的渲染进程(浏览器内核)如何执行异步的。

负责页面的渲染,脚本的执行和时间处理,每一个tab页也都表示一个进程

对于渲染进程来说,它其实就是多线程的:

GUI渲染线程和JS引擎线程互斥:

当JS引擎线程执行时GUI渲染线程会被挂起,GUI更新则会被保存在一个队列中等待JS引擎线程空闲时立即被执行,防止渲染结果不可预期。

2.单线程:

javascript是单线程的,说明在任何一个时间点,JavaScript 的主线程(也就是它的调用栈)只能执行一件任务。如果前一个任务没执行完,后一个任务就必须排队等待。

如果是这样就会导致异步阻塞,很多任务没有办法瞬时完成,比如:

3.异步:

异步就是为了解决单线程的问题,可以实现异步代码不阻塞,当触发到异步代码的时候,把它放到一遍

浏览器内核是如何实现异步呢?

JavaScript本身是单线程的,它实现异步来自于它所运行的环境—通常是浏览器,这个环境提供了一套复杂的机制,我们可以把它里面的一系列线程和机制策略想象成一个分工明确的团队。这个团队由四个核心成员组成:

任务队列又分为两种:

微任务的优先级高于宏任务,当微任务和宏任务一起执行完毕的时候并且调用栈是空的时候,先执行微任务再执行宏任务

setTimeout(() => console.log('Timeout (宏任务)'), 0);
Promise.resolve().then(() => console.log('Promise (微任务)'));
console.log('Script (同步)');
// 输出顺序:
// Script (同步)
// Promise (微任务)
// Timeout (宏任务)

4.回调地狱:

当多个相互依赖的异步操作需要按顺序执行时,开发者将后一个操作的逻辑写在了前一个操作的回调函数中,导致函数调用层层嵌套,形成一种横向扩展、难以阅读和维护的代码结构。

回调地狱会导致什么问题:

1. 可读性极差: 代码不是从上到下线性执行,而是像剥洋葱一样,一层包着一层。人的大脑很难快速理清其中的逻辑顺序和依赖关系。

2. 难以维护: 如果需求变更,比如要在第二步和第三步之间增加一个新的异步操作,你需要小心翼翼地找到正确的位置,插入新的嵌套层,并调整大量的花括号和缩进。这极易出错。

3. 错误处理复杂: `try...catch` 无法跨越异步边界捕获回调函数中的错误。你必须在每一个嵌套层级都单独处理错误(就像上面代码中的 `err1`, `err2`, `err3`),这导致代码非常冗长和重复。

4. 耦合度高: 每一层的逻辑都和上一层的回调紧紧地耦合在一起,很难将某一步的逻辑抽离出来进行复用。

典型的回调地狱:

console.log('开始!');
setTimeout(() => {
  console.log('1秒过去了'); // 第一个回调
  // 为了保证顺序,第二个setTimeout必须嵌套在第一个回调内部
  setTimeout(() => {
    console.log('又2秒过去了'); // 第二个回调
    // 第三个setTimeout必须嵌套在第二个回调内部
    setTimeout(() => {
      console.log('全部完成!'); // 第三个回调
    }, 3000); // 3秒
  }, 2000); // 2秒
}, 1000); // 1秒

解决办法:

1.使用 `Promise` 链式调用:

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
delay(1000)
  .then(() => {
    console.log('1秒过去了');
    // 返回一个新的Promise,以便继续链接.then
    return delay(2000); 
  })
  .then(() => {
    console.log('又2秒过去了');
    return delay(3000);
  })
  .then(() => {
    console.log('全部完成!');
  })
  .catch(err => {
    // 统一处理链条中任何环节可能出现的错误
    console.error('出错了:', err);
  });

2.使用async/await:

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
// 必须在一个 async 标记的函数内部使用 await
async function runTimerSequence() {
  try {
    console.log('开始!');

    await delay(1000); // “等待”1秒,但不会阻塞主线程
    console.log('1秒过去了');

    await delay(2000); // “等待”2秒
    console.log('又2秒过去了');

    await delay(3000); // “等待”3秒
    console.log('全部完成!');

  } catch (err) {
    console.error('出错了:', err);
  }
}
// 执行这个异步函数
runTimerSequence();

总结 

到此这篇关于JavaScript单线程实现异步的文章就介绍到这了,更多相关JS单线程实现异步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文