异步编程
JavaScript单线程特性
JavaScript为什采用单线程?
JavaScript是以
单线程
模式执行代码(同一时间只能做一件事),采用单线程方式工作的原因与其最开始设计初衷有关。JavaScript最早是运行在浏览器的脚本语言,用于处理页面的动态交互,其核心就是dom操作,采用单线程模式是为了避免复杂的多线程同步问题(当同时操作一个dom元素时,无法明确以哪个工作线程为准)。
单线程
指的是JavaScript负责执行代码的线程只有一个,这也就意味着所有任务必须排对执行,采用单线程的工作模式线程利用率极高,可以节省内存,节约上下文切换时间,没有多线程锁的问题,但如果碰到耗时长的任务,就是导致页面阻塞(页面假死)的情况。为了解决这一问题,JavaScript将任务执行模式分成两种———
同步模式
和异步模式
。
同步模式与异步模式
同步模式:排队依次执行。
异步模式:开启执行之后,立即执行下一个任务,后续逻辑在回调函数里面定义。
JavaScript如何实现异步模式的呢?
在代码运行中会同时存在大量的同步任务
和异步任务
,那么JavaScript是如处理同步任务
和异步任务
任务的执行顺序呢?这里就引出了事件轮询(EventLoop)
,它规定了任务在浏览器中的执行顺序,浏览器在运行应用时,通过宏任务/微任务
的类型,将不同的任务放到不同的消息队列
等待执行。
事件轮询/消息队列
宏任务/微任务
宏任务:可以理解为每次执行栈
执行的代码就是一个宏任务。宏任务有————script代码块、setTimeout、setInterval、I/O、UI交互事件、MessageChannel等。
浏览器为了让JavaScript内部宏任务与DOM操作能够有序的执行,
会在一个宏任务执行结束后,下一个宏任务执行开始前,对页面进行重新渲染
。
微任务:每个宏任务执行结束后立即执行的任务,发生在宏任务后,渲染之前,执行微任务。微任务有————Promise.then、MutaionObserver、process.nextTick(Node.js环境下)
微任务的响应速度相比宏任务会更快,因为无需等待UI渲染。
回调函数
回调函数: 由调用者调用,执行者执行。所有异步编程的根本就是回调函数。
Promise
在JavaScript ES6之前,异步编程主要依赖于回调函数。回调函数虽然简单,但存在“回调地狱”(callback hell)的问题,使得代码难以维护。Promise的出现,为异步编程提供了一种更清晰、更结构化的方法。
Promise是一个代表异步操作的对象,它有三种状态:
Pending(进行中):初始状态,既不是成功,也不是失败状态。
Fulfilled(已成功):操作成功完成。
Rejected(已失败):操作失败。
Promise的状态只能从 Pending 变为 Fulfilled 或 Rejected,并且状态一旦改变,就不可逆
。
注意
Promise.then每次返回的是一个状态明确,新创建的promise对象,保证其可以链式调用。
基本应用
function ajax(url){
return new Promise((resolve,reject)=>{
let xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.responseType = 'json';
xhr.onload = function(){
if(this.status === 200){
return resolve(this.response)
}else{
return reject(new Error(this.statusText))
}
}
xhr.onerror = () => reject(new Error('Network error'));
xhr.send();
})
}
ajax('./api/user')
function ajax(url){
return new Promise((resolve,reject)=>{
let xhr = new XMLHttpRequest();
xhr.open('GET',url);
xhr.responseType = 'json';
xhr.onload = function(){
if(this.status === 200){
return resolve(this.response)
}else{
return reject(new Error(this.statusText))
}
}
xhr.onerror = () => reject(new Error('Network error'));
xhr.send();
})
}
ajax('./api/user')
Generator
function* foo() {
yield 'result1'
yield 'result2'
yield 'result3'
}
const gen = foo()
console.log(gen.next().value)
console.log(gen.next().value)
console.log(gen.next().value)
function* foo() {
yield 'result1'
yield 'result2'
yield 'result3'
}
const gen = foo()
console.log(gen.next().value)
console.log(gen.next().value)
console.log(gen.next().value)
Generator需要封装一个执行器
async/await
async/await 是对Generator的语法糖。
async function fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}
async function fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error fetching data:', error);
}
}