异步
进程与线程
进程:拥有资源和独立运行的最小单位,是CPU资源分配的基本单位。进程之间相互独立,互不影响。
线程:建立在进程的基础上的程序运行单位,是CPU调度的最小单位。一个进程中可以有多个线程,同一进程中的线程可以互相通信,共享内存等资源,且可以并行执行。
![]() | 在同一时间内,系统可以运行多个进程。当我们启动一个程序时,CPU便会开启一个进程,并给这个进程分配内存资源。在一个进程中,可以有多个线程同时运行。 |
浏览器是多进程、多线程的
浏览器是多进程的,每个Tab都是一个进程。进程间相互独立,防止某一标签页崩溃卡死而造影响整个浏览器。
浏览器多进程
- Browser进程(主进程):浏览器进程,只有1个,负责主界面显示、处理用户交互、管理其他进程、管理网络资源...
- Renderer进程:渲染进程,每个Tab(标签页)都有一个独立的进程,负责解析并渲染页面、处理JavaScript脚本等。渲染进程是多线程,包括GUI渲染线程、JavaScript引擎线程、事件触发线程、定时触发器线程、异步请求线程。
- 第三方插件进程:插件进程,每种类型的插件对应一个进程,使用插件时创建
- GPU进程:1个,处理应用GPU任务,图形处理、3D绘制...
渲染进程多线程
- GUI渲染线程:渲染浏览器界面(解析
HTML、
CSS、
DOM
树构建、RenderObject
树构建...)、重绘和回流。与JavaScript引擎线程互斥的,当JavaScript引擎执行时,GUI
线程会被挂起,将GUI
更新保存在队列中,等到JavaScript引擎空闲时立即被执行。 - JavaScript引擎线程:处理JavaScript脚本。无论何时,浏览器都只有一个JS线程运行JS程序(主线程),即JavaScript语言的执行是单线程的。
- 事件触发线程:管理事件队列、监听事件。当事件被触发时,该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。
- 定时触发器线程:计时。
setInterval
与setTimeout
在此线程中计时完毕后,把回调函数放入事件队列中,等待JavaScript引擎空闲时执行。 - 异步请求线程:处理http请求。当发送异步Ajax请求时,浏览器会开启异步线程进行http请求,等待服务器响应,该线程监听到请求状态(XMLHttpRequest对象)变化,会产生状态变更事件,回调函数放入事件队列,等待JavaScript引擎执行。
JavaScript是单线程的
JavaScript的运行环境(浏览器或Node.js 环境),只有一个主线程来处理所有的任务。所有的任务都需要排队;在同一时间,只能处理一个任务,等执行完后,才能执行下一个。
但如果遇到执行时间长的任务,就会阻塞后续排队的任务执行。因此引入了异步编程模型,通过事件循环机制(Event Loop)、回调函数、Promise对象/async/await语法实现异步编程,使程序可以在等待完成的同时,继续执行其他任务
同步任务和异步任务
js代码开始执行后,主线程执行栈会把任务分成两类:同步任务和异步任务
- 同步任务:主线程上的任务,按顺序依次执⾏。
- 异步任务:进入"任务队列"的任务。异步任务的执行不会阻塞调用栈中的同步任务。
任务队列:先进先出的数据结构,又称消息队列/事件队列。存放js单线程所遇到的异步任务,可以有多个。
JavaScript会将异步任务交给宿主环境(浏览器或Node.js)来处理,待异步任务完成后,将其回调函数放入任务队列中并通知主线程,等待执行,等主线程上的任务执行完后,就会调取最早通知自己的回调函数,使其进入主线程中执行。
异步任务中的宏任务和微任务
在ES6以前,JavaScrip未定义异步请求API,依赖于宿主环境(浏览器的XMLHttpRequest、Node.js的I/O 模块));任务队列仅有宏任务,不存在微任务机制。
ES6新增Promise,实现微任务队列机制,JavaScript引擎可自主调度微任务,但具体的异步操作(如网络请求)仍需宿主环境提供API。
异步任务细分为宏任务、微任务
- 宏任务(macro-task):被放到宏任务队列中等待执行;整体代码script、setTimeout / setInterval、Ajax请求、UI渲染、I/O操作、setImmediate(Node.js环境)...
- 微任务(micro-task):被放到微任务队列中等待执行;Promise的then/catch/finally回调、await后面的代码、MutationObserver(HTML5,监听DOM树变化)、process.nextTick(Node.js环境)、queueMicrotask...
事件循环机制
事件循环执行机制(Event Loop)
- 执行首个宏任务script
- 立刻执行script中同步代码;遇到宏任务,放入到宏任务队列里;遇到微任务,放入到微任务队列里
- script中同步代码执行结束,先清空微任务队列
- 再取出下一个宏任务执行,执行该宏任务中的同步代码,将该宏任务中产生的宏任务和微任务放到对应队列
- 宏任务中同步代码执行完,先清空微任务队列
- 依次循环执行4、5,直到所有任务执行完毕
补充:微任务队列只有一个,宏任务队列可以有多个(多宏任务可能分优先级)
异步编程
Ajax
Ajax:Asynchronous Javascript And XML(异步 JavaScript 和 XML)。通过XMLHttpRequest实现Ajax,也可以使用 fetch API 等现代方法。可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。
发送Ajax请求
- 创建异步对象XMLHttpRequest
- 注册onreadystatechange事件,同时在事件处理函数增加校验readyState===4和status===200。当XMLHttpRequest状态变化时(服务端响应),调用onreadystatechange函数
- 使用open方法初始化请求:指定请求方法(如GET/POST)、目标URL、是否异步(默认为true,true异步/false同步)
- (可选)设置请求头:POST需要通过setRequestHeader设置Content-Type
- 使用send方法发送请求,GET方法传参null,POST传参数据
get 请求
const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); // 1.创建XMLHttpRequest对象,ActiveXObject兼容IE5/IE6
xhr.onreadystatechange = function () { // 2.注册onreadystatechange事件,XMLHttpRequest状态改变时调用
if (xhr.readyState === 4 && xhr.status === 200) { // 5.服务器响应,数据请求成功
console.log('数据返回成功:' + JSON.stringify(xhr.responseText));
// 使用XMLHttpRequest的responseText或responseXML属性获得来自服务器的响应
// responseText获得字符串形式的响应数据
// responseXML获得XML形式的响应数据
}
};
xhr.open('get', 'api/data', true); // 3.初始化请求:设置参数
xhr.send(); // 4.发送请求
post请求
const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); // 1.创建XMLHttpRequest对象
xhr.onreadystatechange = function () { // 2.注册onreadystatechange事件
if (xhr.readyState === 4 && xhr.status === 200) { // 6.服务器响应,数据请求成功
console.log('数据返回成功:' + JSON.stringify(xhr.responseText));
}
};
xhr.open('post', 'api/data', true); // 3.初始化请求
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); // 4.设置请求头
xhr.send('username=lq&action=submit'); // 5.发送请求
封装Ajax请求
// 封装get请求Ajax
function getAjax(url, success, fail) {
const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
success && success(xml.responseText);
}else{
fail && fail(new Error('接口请求失败'));
}
};
xml.open('get', url, true);
xml.send();
}
//调用
getAjax('api/data', (res) => { console.log(res); });
// 封装 post请求Ajax
function postAjax(url, data, success, fail) {
const xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
success && success(xml.responseText);
} else {
fail && fail(new Error('接口请求失败'));
}
};
xhr.open('post', 'api/data', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xml.send(data);
}
// 调用
postAjax('api/data', 'username=lq&action=submit', (res) => { console.log(res); });
JSON
JSON:一种轻量级的数据交换格式。用于前后端之间的数据传输,也被用于存储和交换数据
<支持数字(整数或浮点数)、字符串(双引号包裹)、布尔值、数组、对象>
JSON示例
{
"supportString": "JSON",
"supportBoolean": true,
"supportNumber": 2,
"supportArray": [
{
"name1": "value1"
},
{
"name2": "value2"
}
],
"supportObject": {
"name": "value"
}
}
JSON.stringify(Object数据):将JavaScript对象变成 JSON(序列化成一个 JSON 格式的字符串)
JSON.parse(json数据):将JSON字符串格式化为JavaScript对象
同源与跨域
同源策略:浏览器的一种安全策略,不允许非同源的URL之间进行资源的交互。
同源:指两个URL的域名、协议、端口都相同;只要有一项不同就是跨域。
Promise
为避免回调地狱(层层嵌套异步回调),可以更优雅地书写复杂的异步任务,ES6新增了Promise
Promise:是一个处理异步操作的对象,有三种状态:pending(进行中)、fulfilled(已完成)、rejected(已失败)。
Promise实例的方法
then(处理成功状态)、catch(处理失败状态)、finally(无论成功还是失败)
处理异步操作
通过new Promise创建promise实例时,构造函数的参数(执行器函数)会被立即执行,此时promise状态初始化为pending;
如果异步任务成功,调用resolve函数将promise状态变为fulfilled,此时会触发promise实例then方法的第一个参数(成功的回调函数),对成功的返回结果进行处理;
如果异步任务失败,调用reject函数将promise状态变为rejected,此时会触发promise实例的then方法中的第二个参数(失败回调函数),对失败的返回结果进行处理,或promise实例的catch方法捕获错误;
!状态从pending转换为fulfilled或rejected不可逆
// 通过构造函数Promise,创建一个promise实例对象
// Promise构造函数接受一个回调函数(被称为执行器函数)作为参数,在new Promise时被立即执行;promise状态被初始化为pending
// 执行器函数包含两个形参resolve和reject,用于将当前promise实例的状态从pending变为fulfilled或rejected
// 创建 promise 实例
const myPromise = new Promise((resolve, reject) => {
// promise状态pending
// 开始执行异步操作(例如ajax请求或开启定时器...)
if ('异步操作成功') {
// 如果异步任务成功,调用resolve函数,将promise状态变为fulfilled
resolve('数据');
} else {
// 如果异步任务失败,调用reject函数,将promise状态变为rejected
reject('错误信息');
}
});
// 处理成功和失败,写法1
promise.then(result => {
// 如果promise的状态为fulfilled,执行这里的代码
}).catch(error => {
// 如果promise的状态为rejected,则执行这里的代码
});
// 处理成功和失败,写法2
// promise.then(
// result => {
// // promise的状态为fulfilled执行,处理promise的成功状态,result为resolve函数传过来的参数
// },
// error => {
// // promise的状态为rejected执行,处理promise的成功状态,error为reject函数传过来的参数
// }
// );
// 写法1和写法2稍有区别
// myPromise.then(onFulfilled).catch(onRejected):既可以捕获到myPromise的异常,也可以捕获到 then()里面的异常
// myPromise.then(onFulfilled, onRejected):只能捕获到myPromise的异常,无法捕获then()里面的异常
// myPromise的状态变为失败时,它会找到最近的那个失败回调函数并执行
then的链式调用
then()方法的返回值永远是一个 Promise 对象,所以可以对它进行链式调用
const myPromise = new Promise((resolve, reject) => {
resolve('success');
});
myPromise
.then(res => {
console.log('myPromise fulfilled', '--', res); // myPromise fulfilled -- success
// 当then方法中的回调函数中,未设定返回值,默认为该then()返回:new Promise((resolve, reject) => { resolve(); }) ;
// then()返回promise的状态为fulfilled
})
.then(res => {
console.log('then 1 return promise fulfilled', '--', res); // then 1 return promise fulfilled -- undefined
// 当then方法中的回调函数中,设置返回值是普通值/普通对象(没有then方法),该then()返回:new Promise((resolve, reject) => { resolve(设置返回值); }) ;
// then()返回promise的状态为fulfilled
return {info: 'objectInfo'};
})
.then(res => {
console.log('then 2 return promise fulfilled', '--', res); // then 2 return promise fulfilled -- {info: 'objectInfo'}
// 当then方法中的回调函数中,设置返回值为thenable对象(有then方法),会立即执行对象中then方法,并根据结果来决定then()方法返回的promise对象状态
return { then: (resolve, reject) => { resolve('return a fulfilled thenable'); } };
})
.then(res => {
console.log('then 3 return promise fulfilled', '--', res); // then 3 return promise fulfilled -- return a fulfilled thenable
// 当then方法中的回调函数中,设置返回值是一个新的promise,then()返回promise的状态取决于新的promise
return new Promise((resolve, reject) => { resolve('return 成功的 promise'); })
})
.then(res => {
console.log('then 4 return promise fulfilled', '--', res); // then 4 return promise fulfilled -- return 成功的 promise
return new Promise((resolve, reject) => { reject('return 失败的 promise'); })
})
.then(res => {
console.log('then 5 return promise fulfilled', '--', res);
}, err => {
// 第5个then()返回promise的状态为rejected,执行失败的回调函数
console.log('then 5 return promise rejected', '--', err); // then 5 return promise rejected -- return 失败的 promise
})
.then(res => {
// 第6个then()返回promise的状态为fulfilled,执行成功的回调函数
console.log('then 6 return promise fulfilled', '--', res); // then 6 return promise fulfilled -- undefined
}, err => {
console.log('then 6 return promise rejected', '--', err);
})
// 补充:当then()方法传入的回调函数遇到异常或者手动抛出异常时,那么,then()所返回的新的promise会进入rejected状态
catch的链式调用
与then()方法类似,catch()方法也会返回一个新的Promise对象,可以对它进行链式调用
const myPromise = new Promise((resolve, reject) => {
reject('failed');
});
myPromise
.catch(err => {
console.log('myPromise rejected', '--', err); // myPromise rejected -- failed
// 当catch方法中的回调函数中,未设定返回值,默认为该catch()返回:new Promise((resolve, reject) => { resolve(); }) ;catch()返回promise的状态为fulfilled
// 当catch方法中的回调函数中,设置返回值是普通值,该catch()返回:new Promise((resolve, reject) => { resolve(设置返回值); }) ;catch()返回promise的状态为fulfilled
// 当catch方法中的回调函数中,设置返回值为thenable对象(有then方法),会立即执行对象中then方法,并根据结果来决定catch()方法返回的promise对象状态
// 当catch方法中的回调函数中,设置返回值是一个新的Promise,catch()返回promise的状态取决于新的promise
// 当catch方法传入的回调函数,遇到异常或者手动抛出异常时,那么,catch()返回promise的状态处于rejected状态
return 'catchSetReturnValue';
})
.then(res => {
console.log('catch return promise fulfilled', '--', res); // catch return promise fulfilled -- catchSetReturnValue
})
补充:当promise抛出rejected异常时,一定要捕获并处理;如果不处理失败的回调,会报错;建议在末尾写一个 catch() 方法
Promise类的方法
- Promise实例的方法:需要先将Promise实例化,存放在Promise的prototype上
- Promise类的方法:可以直接通过大写的
Promise.xxx
调用的方法,也称Promise的静态方法
Promise 类的方法
Promise.resolve() | 返回一个成功状态的Promise对象 |
Promise.reject() | 返回一个失败状态的Promise对象 |
Promise.resolve('promise fulfilled').then(res => console.log('Promise.resolve:', res)); // Promise.resolve: promise fulfilled
Promise.reject('promise rejected').catch(err => console.log('Promise.reject:', err)); // Promise.reject: promise rejected
Promise.race() | 返回跟与第一个执行完成Promise状态相同的Promise,并将结果传递给对应状态的回调函数 |
Promise.any() | 当存在fulfilled状态的Promise时,返回一个成功的Promise,并将第一个执行为fulfilled状态Promise的结果传递给成功的回调; 当所有Promise均为rejected时,传递给失败回调一个 |
Promsie.all() | 当所有Promise均为fulfilled时,返回一个成功的Promise,并向成功的回调传递:一个包含所有Promise结果的数组; 只要有一个Promise为rejected,就返回一个失败的Promise,并向失败的回调传递:第一个执行为reject状态Promise的结果 |
Proimse.allSettled() | 等待所有的Promise都有结果后,返回一个成功的Promise,并向成功的回调传递:一个包含所有Promise的结果的数组 |
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise1');
reject('promise 1 失败');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise2');
resolve('promise 2 成功');
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('执行 promise3');
resolve('promise 3 成功');
}, 3000);
});
Promise.race([promise1, promise2, promise3])
.then((res) => { console.log(res); })
.catch((err) => { console.log(err); })
// 执行结果
// 1s后:执行 promise1
// promise 1 失败
// 2s后:执行 promise2
// 3s后:执行 promise3
Promise.any([promise1, promise2, promise3])
.then((res) => { console.log(res); })
.catch((err) => { console.log(err); })
// 执行结果
// 1s后:执行 promise1
// 2s后:执行 promise2
// 执行 promise2
// 3s后:执行 promise3
Promise.all([promise1, promise2, promise3])
.then((res) => { console.log(res); })
.catch((err) => { console.log(err); })
// 执行结果
// 1s后:执行 promise1
// promise 1 失败
// 2s后:执行 promise2
// 3s后:执行 promise3
Promise.all([promise2, promise3])
.then((res) => { console.log(res); })
.catch((err) => { console.log(err); })
// 执行结果
// 2s后:执行 promise2
// 3s后:执行 promise3
// ['promise 2 成功', 'promise 3 成功']
Promise.allSettled([promise1, promise2, promise3]).then(res => {
console.log(res);
})
// 执行结果
// 1s后:执行 promise1
// 2s后:执行 promise2
// 3s后:执行 promise3
// [
// { status: "rejected", reason: "promise 1 失败" },
// { status: "fulfilled", value: "promise 2 成功" },
// { status: "fulfilled", value: "promise 3 成功" }
// ]
async异步函数
异步函数:指用async关键字声明的函数默认情况下会同步执行。需要搭配await关键字使用,才能发挥异步执行的作用
声明异步函数
// 写法1:函数声明的写法
async function foo1() { }
// 写法2:表达式写法(ES5语法)
const foo2 = async function () { }
// 写法3:表达式写法(ES6箭头函数语法)
const foo3 = async () => { }
// 写法4:定义一个类,在类中添加一个异步函数
class className {
async foo4() { }
}
异步函数的返回值永远是一个 Promise 对象
// 当异步函数内部中,未设定返回值,返回promise的状态为fulfilled,相当于return new Promise((resolve, reject) => { resolve(); })
// 当异步函数内部中,返回值为普通值,返回promise的状态为fulfilled,相当于return new Promise((resolve, reject) => { resolve(设置返回值); }) ;
// 当异步函数内部中,返回值为thenable对象(有then方法),会立即执行对象中then方法,并根据结果来决定返回的promise的状态
// 当异步函数内部中,返回值为新的Promise,返回promise的状态取决于新的promise
// 当异步函数内部,执行时遇到异常或者手动抛出异常时,那么,返回promise的状态处于rejected状态
const foo = async function () { return 'data' }
foo().then(res => { console.log(res); }) // data
async/await
在async声明的异步函数中,可以使用await关键字来暂停异步函数的执行,等待await后面的异步操作完成。
- await 的后面是一个表达式(Promise对象),await执行完成后可以得到异步结果
- await 会等到当前 Promise 的状态变为 fulfilled之后,才继续往下执行异步函数
// requestData为一个异步操作
const getData = async function () {
const result = await requestData();
// 执行完成await后面的异步操作,才可以继续执行下面的代码
console.log(result);
}
getData();
异步函数的异常处理
- 方式1:通过 Promise的catch()方法捕获异常。
- 方式2:通过 try catch捕获异常。
function requestData1() {
return new Promise((resolve, reject) => { reject('任务1失败'); })
}
function requestData2() {
return new Promise((resolve, reject) => { resolve('任务2成功'); })
}
const getData = async function () {
try {
// requestData1 在执行时,遇到异常
await requestData1();
/*
由于上面的代码在执行是遇到异常,当前任务立即终止,所以,这里虽然什么都没写,底层默认写了如下代码:
return Promise.reject('任务1失败');
*/
// 下面这两代码不会再走了
console.log('qianguyihao1');
await requestData2();
}
catch (err) {
// 捕获异常代码
console.log('err:', err);
}
}
getData();
补充:同步代码的异常处理:可通过 throw 抛出异常
// 可以在throw后面添加表达式或者数据类型,将添加的内容抛出去。数据类型可以是:number、string、boolean、对象等。
function sum(num1, num2) {
if (typeof num1 !== "number") {
throw "type error: num1传入的类型有问题, 必须是number类型"
}
if (typeof num2 !== "number") {
throw "type error: num2传入的类型有问题, 必须是number类型"
}
return num1 + num2
}
sum('a', 'b');
// 执行结果:(控制台报错) Uncaught type error: num1传入的类型有问题, 必须是number类型