尼采般地抒情

尼采般地抒情

尼采般地抒情

音乐盒

站点信息

文章总数目: 318
已运行时间: 1825

前言:JavaScript的事件循环机制或是异步编程是具体代码编写的重点,JavaScript语言的单线程执行特点以及同步任务和异步任务,由这些语言特性共同有一套事件循环执行机制也就是异步代码的预期执行效果。

一、JS代码执行机制

JavaScript的单线程

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。这样所导致的问题是: 如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

同步任务和异步任务

单线程导致的问题就是后面的任务等待前面任务完成,如果前面任务很耗时(比如读取网络数据),后面任务不得不一直等待。为了解决这个问题,利用多核 CPU 的计算能力,HTML5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制。于是JS 中出现了同步任务和异步任务。

JS中所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。

  • 【同步任务】指的是:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 【异步任务】指的是:不进入主线程、而进入“任务队列”的任务,当主线程中的任务运行完了,才会将异步任务相关的回调函数从”任务队列”取出异步任务放入主线程执行。

异步任务又分为宏任务和微任务

常见的宏任务有:

  1. setTimeout
  2. setInterval
  3. I/O(磁盘读写、网络通信···)
  4. setImmediate(浏览器环境没有、Nodejs环境有)
  5. requestAnimationFrame(浏览器环境有、Nodejs环境没有)

常见的微任务有:

  1. Promise
  2. Object.observe
  3. process.nextTick (浏览器环境没有、Nodejs环境有)
  4. MutationObserver(浏览器环境有、Nodejs环境没有)

事件循环机制

事件循环机制则是按照上面所描述的JavaScript特性,当即有同步任务又有异步任务情况下,代码的执行先后顺序。

  1. 碰到同步任务,就先执行执行栈中的同步任务

遇到函数的嵌套调用就把函数压入栈内再依次“剥洋葱”

  1. 碰到异步任务就压入任务队列(异步任务分为宏任务和微任务)
  2. 当前执行栈中的所有同步任务执行完毕,就将执行异步任务,异步任务执行原则“先微后宏”

由于主线程不断地重复获得任务、执行任务、再获取任务、再执行……,这种机制被称为事件循环(Event Loop)

二、异步编程

并发执行

  • 如果await之间没有相关依赖或顺序关系,可以直接用Promise.all包裹起来并发执行
// ⛔
async function loadDashboard() {
  const user = await fetchUser();
  const posts = await fetchPosts();
  const notifications = await fetchNotifications();
}
// ✅ 并发执行
async function loadDashboard() {
  const [user, posts, notifications] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchNotifications()
  ]);
}

以下情况不建议使用并发执行:

  • 依赖关系:如果一个请求需要另一个请求返回的数据,就必须顺序执行。
  • 竞态条件:如果多个请求会同时修改某个共享资源,可能导致数据不一致。
  • 资源限制:并发过多会给服务器或浏览器带来压力。

学会在try块中抛出错误

    • throw new Error(`HTTP error! status: ${response.status}`);
    • throw new ApiError('Failed to fetch critical data', { cause: error });

轮询请求异步接口

// ⛔ 隐形的内存杀手
async function startPolling() {
  setInterval(async () => {
    const data = await fetchData();
    updateUI(data);
  }, 5000);
}

存在以下问题:

  • 组件或页面卸载后依然在轮询
  • 如果 fetchData() 执行得很慢,可能会同时发起多次请求
  • 更新 UI 时,目标 DOM 甚至可能已经被移除
class PollingManager {
  constructor(options = {}) {
    this.controller = new AbortController();
    this.interval = options.interval || 5000;
  }
  async start() {
    while (!this.controller.signal.aborted) {
      try {
        const response = await fetch('/api/data', {
          signal: this.controller.signal
        });
        const data = await response.json();
        updateUI(data);
        
        // 等待下一次轮询
        await new Promise(resolve => setTimeout(resolve, this.interval));
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Polling stopped');
          return;
        }
        console.error('Polling error:', error);
      }
    }
  }
  stop() {
    this.controller.abort();
  }
}
// 在 React 组件中使用
function DataComponent() {
  useEffect(() => {
    const poller = new PollingManager({ interval: 5000 });
    poller.start();
    // 组件卸载时停止轮询
    return () => poller.stop();
  }, []);
  return <div>Data: {/* ... */}</div>;
}

通过使用 AbortController,可以在需要时终止请求并及时释放资源,更好地控制组件的生命周期和内存占用。


三、一些例子

通过一些实际代码例子及其运行的结果来加深对事件循环机制的理解

同步代码+Promise

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve("success");
  console.log(2);
});
promise.then((data) => {
  console.log(data);
  console.log(3);
});
console.log(4);

const promise1 = new Promise((resolve, reject) => {
  console.log("promise1");
  resolve("resolve1");
});
const promise2 = promise1.then((res) => {
  console.log(res);
});
console.log("1", promise1);
console.log("2", promise2);

同步代码+setTimeout+Promise

console.log(1);
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
  });
});
console.log(4);
new Promise((resolve, reject) => {
  console.log(5);
  resolve();
})
  .then(() => {
    console.log(6);
    setTimeout(() => {
      console.log(7);
    });
  })
  .catch(() => {
    console.log(9);
  });
console.log(8);

setTimeout(() => {
  console.log(1);
}, 0);
console.log(2);
const p = new Promise((resolve) => {
  console.log(3);
  resolve();
})
  .then(() => {
    console.log(4);
  })
  .then(() => {
    console.log(5);
  });
console.log(6);

then的连续回调都属于一个微任务

new Promise((resolve,reject)=>{
  console.log(1)
  resolve()
}).then(()=>{
  console.log(2)
  new Promise((resolve,reject)=>{
    console.log(3)
    resolve()
  }).then(()=>{
    console.log(4)
  }).then(()=>{
    console.log(5)
  })
}).then(()=>{
  console.log(6)
})

new Promise((resolve, reject) => {
  console.log(1);
  resolve();
})
  .then(() => {
    console.log(2);
    return new Promise((resolve, reject) => {
      console.log(3);
      resolve();
    })
      .then(() => {
        console.log(4);
      })
      .then(() => {
        console.log(5);
      });
  })
  .then(() => {
    console.log(6);
  });

new Promise((resolve, reject) => {
  console.log(1)
  resolve()
}).then(() => {
  console.log(2)
  new Promise((resolve, reject) => {
    console.log(3)
    resolve()
  }).then(() => {
    console.log(4)
  }).then(() => {
    console.log(5)
  })
}).then(() => {
  console.log(6)
})
new Promise((resolve, reject) => {
  console.log(7)
  resolve()
}).then(() => {
  console.log(8)
})

同步代码+async await+Promise

await可以看成是Promise的语法糖(实际上是Generate的语法糖),效果和Promise的回调地狱一样,只是代码书写起来类似同步执行代码。

async function async1() {
  console.log(1);
  await async2();
  console.log(2);
}
async function async2() {
  console.log(3);
}
console.log(4);
setTimeout(function () {
  console.log(5);
});
async1();
new Promise(function (resolve, reject) {
  console.log(6);
  resolve();
}).then(function () {
  console.log(7);
});
console.log(8);

综合例子

async function a1() {
  console.log('a1 start')
  await a2()
  console.log('a1 end')
}
async function a2() {
  console.log('a2')
}

console.log('script start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
  console.log('promise1')
})

a1()

let promise2 = new Promise(resolve => {
  resolve('promise2.then')
  console.log('promise2')
})

promise2.then(res => {
  console.log(res)
  Promise.resolve().then(() => {
    console.log('promise3')
  })
})
console.log('script end')

可视化工具

评论区

什么都不舍弃,什么也改变不了