文章目录
1.异步操作
1.1 什么是单线程?
JavaScript是单线程语言。只有一个线程,同时只能做一件事,两段js不能同时执行。
1.2 为什么JavaScript是单线程?
为了避免DOM渲染冲突;
1.3 异步出现的原因?
解决因同步执行代码阻塞时间过长而导致的页面卡死情况
2.Event Loop(事件轮询)
2.1 Event Loop 的理解
-
Event Loop
是javascript的执行机制,用于等待
和发送消息
的事件,是实现异步
的具体解决方案 -
在程序中设置两个线程:一个负责程序本身的运行,称为"
主线程
";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"EventLoop线程"(可以译为"异步线程
")。
2.2 Event Loop 运行流程
ES6标准中,异步任务又分为两种类型,宏任务
(macrotask)和微任务
(microtask)。
宏任务:由宿主环境提供,在浏览器环境执行,比如:setTimeout
、setInterval
、网络请求
、用户I/O、script(整体代码)
、UI rendering、setImmediate(node)。
微任务:语言标准提供,在js引擎环境执行,如:process.nextTick
(node环境中,要先于其他微任务执行)、Promise
、Object.observe、MutationObserver。
- 同步代码(主线程)直接执行;
- 异步函数先放在异步队列中。任务队列的读取顺序是先读取所有微观任务队列执行后再读取一个宏观任务队列,再读取所有微观任务队列,再读取一个宏观任务队列…)
- 待同步函数执行完毕,轮询执行 异步队列中的函数;
- 以上步骤不断重复执行,就形成了事件轮询;
3. settimeout和setinterval
settimeout
和setinterval
也是异步操作,该操作里面的内容会延迟操作。
4. XHR(XMLHttpRequest)
XMLHttpRequest
用于在后台与服务器交换数据- 通过
XMLHttpRequest
可以在不刷新页面的情况下请求特定 URL,获取数据。 - 这允许网页在不影响用户操作的情况下,更新页面的局部内容。
XMLHttpRequest
在AJAX
编程中被大量使用。
this.xhr = new XMLHttpRequest(); // 创建 XMLHttpRequest 对象
this.xhr.open('post', url, true); // post方式,url为服务器请求地址,true 该参数规定请求是否异步处理。
this.xhr.onload = evt => this.uploadComplete(evt); // 请求完成
this.xhr.onloadstart = () => this.progressFunction(uuid); // 请求开始
this.xhr.upload.onprogress = () => this.progressFunction(uuid); // 请求中
this.xhr.onerror = this.uploadFailed; // 请求失败
this.xhr.send(formData); // 开始上传,发送form数据
5 AJAX
Asynchronous JavaScript + XML(异步JavaScript和XML)。
Ajax 是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新。
$.ajax({
type : "GET", //提交方式
url : web2, //路径,www根目录下
datatype: "json", //数据类型
async: false, //是否为异步操作
success : function(result) { //返回数据根据结果进行相应的处理
var accountname = result.attributes.accountname;
alert(accountname);
}
});
XMLHttpRequest
模块就是实现 Ajax 的一种很好的方式,利用 XMLHttpRequest
模块实现 Ajax:
<body>
<script>
function ajax(url) {
const xhr = new XMLHttpRequest();
xhr.open("get", url, false);
xhr.onreadystatechange = function () {
// 异步回调函数
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.info("响应结果", xhr.response)
}
}
}
xhr.send(null);
}
ajax('https://smallpig.site/api/category/getCategory')
</script>
</body>
6. promise
- 是ES6提出的异步编程的一种解决方案。
- 从语法上说,
Promise
是一个对象,从它可以获取异步操作的消息。
6.1 Promise 对象
- 用同步的方式来书写异步代码
Promise
让异步操作写起来,像在写同步操作的流程,解决了回调地狱的问题(不必一层层地嵌套回调函数)- 改善了可读性,对于多层嵌套的回调函数很方便
- 充当异步操作与回调函数之间的中介,使得异步操作具备同步操作的接口
function fn() {
return new Promise((resolve, reject) => {
resolve(result) //成功时调用 resolve
reject(error) //失败时调用 reject
})
}
fn().then(success, fail).then(success2, fail2).catch(fail3);
6.2 Promise 状态
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
const p1 = new Promise(function(resolve,reject){
resolve('success1');
resolve('success2');
});
const p2 = new Promise(function(resolve,reject){
resolve('success3');
reject('reject');
});
p1.then(function(value){
console.log(value); // success1
});
p2.then(function(value){
console.log(value); // success3
});
6.3 then 方法
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
可以添加多个回调函数,它们会按照插入顺序并且独立运行。
const p = new Promise(function(resolve,reject){
resolve(1);
}).then(function(value){ // 第一个then // 1
console.log(value);
return value * 2;
}).then(function(value){ // 第二个then // 2
console.log(value);
}).then(function(value){ // 第三个then // undefined
console.log(value);
return Promise.resolve('resolve');
}).then(function(value){ // 第四个then // resolve
console.log(value);
return Promise.reject('reject');
}).then(function(value){ // 第五个then //reject:reject
console.log('resolve:' + value);
}, function(err) {
console.log('reject:' + err);
});
6.4 Promise.all
Promise.all
需要传入一个数组,数组中的元素都是Promise
对象。- 当这些对象都执行成功时,则 all 对应的 promise 也成功,且执行 then 中的成功回调。
- 如果有一个失败了,则 all 对应的 promise 失败,且失败时只能获得第一个失败 Promise 的数据。
const p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
const p2 = Promise.resolve('success')
const p3 = Promise.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //["成功了", "success"]
}).catch((error) => {
//未被调用
})
Promise.all([p1, p3, p2]).then((result) => {
//未被调用
}).catch((error) => {
console.log(error) //"失败"
});
6.5 Promise.race
Promise.race()
里面哪个 Promise
对象最快得到结果,就返回那个结果,不管结果本身是成功状态还是失败状态。
const p1 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "one");
});
const p2 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "two");
});
Promise.race([p1, p2]).then((result) => {
console.log(result); // "two"
// 两个都完成,但 p2 更快
});
const p3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, "three");
});
const p4 = new Promise(function(resolve, reject) {
setTimeout(reject, 500, "four");
});
Promise.race([p3, p4]).then((result) => {
console.log(result); // "three"
// p3 更快,所以它完成了
}, (error) => {
// 未被调用
});
const p5 = new Promise(function(resolve, reject) {
setTimeout(resolve, 500, "five");
});
const p6 = new Promise(function(resolve, reject) {
setTimeout(reject, 100, "six");
});
Promise.race([p5, p6]).then((result) => {
// 未被调用
}, error => {
console.log(error); // "six"
// p6 更快,所以它失败了
});
7 fetch
Fetch 是在 ES6 出现的,它使用了 ES6 提出的 promise
对象。它是 XMLHttpRequest
的替代品。
Fetch 是一个 API,它是真实存在的,它是基于 promise 的。
fetch('./api/some.json')
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
7.1 fetch优势
- 语法简洁,更加语义化
- 基于标准
Promise
实现,支持async/await
- 同构方便,使用 isomorphic-fetch
- 更加底层,提供的API丰富(request, response)
- 脱离了
XHR
,是ES规范里新的实现方式
7.2 fetch存在问题
- fetch是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装。
- fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: ‘include’})
- fetch没有办法原生监测请求的进度,而XHR可以
7.3 取消 fetch 请求
使用情况:两次查询请求相隔时间很短时,由于接口异步,第一次请求可能会覆盖第二次请求返回数据,所以需要在第二次请求前先将第一次请求中止
- 创建一个
AbortController
实例 - 这个实例拥有一个
signal
属性 - 把这个
signal
作为fetch
请求的选项 - 调用
AbortController
的abort
属性以取消所有使用这个信号的fetch
请求。
// 获取中断信号
getAbortSignal(){
if(window.AbortController) {
window._abortController = new AbortController();
}
return window._abortController?.signal || null;
},
// 中断未完成 fetch 请求
abortFetchRequest() {
if(window._abortController?.abort) {
window._abortController.abort();
}
},
// fetch函数网络请求的封装
fetchRequest(url, params, successCallBack, failureCallBack, isRefreshToken = true, isAbortFetch = false) {
if(isAbortFetch) {
this.abortFetchRequest();
}
if (!isRefreshToken || tokenUtil.tokenEnable()) {
let token = {};
if (localStorage.getItem("token")) {
token = JSON.parse(localStorage.getItem("token"));
}
params.nonce = this.generateUUID();
params.timestamp = new Date().getTime();
params.sign=CryptoJS.MD5(params.nonce + params.timestamp + config.replySecret).toString();
const { access_token = "" } = token;
const postParams = {
method: 'POST',
mode: 'cors',
headers: {
Authorization: access_token,
Accept: 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
},
body: JSON.stringify(params),
credentials: 'include',
signal: this.getAbortSignal(),
};
fetch(url, postParams)
.then(response => {
if (isRefreshToken && response.status === 401) {
tokenUtil.refreshTokenForFetch(url, params, successCallBack, failureCallBack);
return;
}
if (response.ok) {
response.json().then((json) => {
// 网络连接成功的回调
successCallBack(json);
});
} else {
failureCallBack('请求失败');
}
})
.catch(err => {
if(!err?.toString().includes('AbortError')) { // 手动中断请求不抛异常
failureCallBack(`请求异常${err}`);
}
});
} else {
tokenUtil.refreshTokenForFetch(url, params, successCallBack, failureCallBack);
}
},
8. axios
axios 是一个基于 promise 的 HTTP 封装库,可以用在浏览器和 node.js 中。本质也是对XHR的封装,底层可以选择xhr或者fetch进行请求
使用Axios的好处:
- 在浏览器中发送XMLHttpRequests请求
- 在node.js中发送http请求
- 支持Promise API
- 拦截请求和响应
- 转换请求和相应数据
8.1 axios基本使用
# 1. 发送get 请求
axios.get('http://localhost:3000/adata').then(function(ret){
# 拿到 ret 是一个对象 所有的对象都存在 ret 的data 属性里面
// 注意data属性是固定的用法,用于获取后台的实际数据
// console.log(ret.data)
console.log(ret)
})
# 2. get 请求传递参数
# 2.1 通过传统的url 以 ? 的形式传递参数
axios.get('http://localhost:3000/axios?id=123').then(function(ret){
console.log(ret.data)
})
# 2.2 restful 形式传递参数
axios.get('http://localhost:3000/axios/123').then(function(ret){
console.log(ret.data)
})
# 2.3 通过params 形式传递参数
axios.get('http://localhost:3000/axios', {
params: {
id: 789
}
}).then(function(ret){
console.log(ret.data)
})
#3 axios delete 请求传参 传参的形式和 get 请求一样
axios.delete('http://localhost:3000/axios', {
params: {
id: 111
}
}).then(function(ret){
console.log(ret.data)
})
# 4 axios 的 post 请求
# 4.1 通过选项传递参数
axios.post('http://localhost:3000/axios', {
uname: 'lisi',
pwd: 123
}).then(function(ret){
console.log(ret.data)
})
# 4.2 通过 URLSearchParams 传递参数
var params = new URLSearchParams();
params.append('uname', 'zhangsan');
params.append('pwd', '111');
axios.post('http://localhost:3000/axios', params).then(function(ret){
console.log(ret.data)
})
#5 axios put 请求传参 和 post 请求一样
axios.put('http://localhost:3000/axios/123', {
uname: 'lisi',
pwd: 123
}).then(function(ret){
console.log(ret.data)
})
8.2 axios全局配置
开发中,很多参数是固定的. 可以进行一些抽取, 也可以利用axiox的全局配置
axios.defaults.baseURL = 'tp://000.000'
axios.defaults.headers.post['Content-Type'] = 'xxxxx'
export default {
name: 'app',
created () {
// 提取全局的配置
axios.defaults.baseURL = 'http://000.000'
// 发送并发请求
axios.all([
axios.get('/xxxx'),
axios.get('/xxxxx', {
params: {
type: 'sell',
page: 1
}}
)
]).then(axios.spread((rea1, res2) => {
console.log(res1);
console.log(res2);
}))
}
}
8.3 常见的配置选项
请求地址 url: ‘/user’,
请求类型 method: ‘get’,
请根路径 baseURL: ‘http://www.mt.com/api’,
请求前的数据处理 transformRequest:[function(data){}],
请求后的数据处理 transformResponse: [function(data){}],
自定义的请求头 headers:{‘x-Requested-With’:‘XMLHttpRequest’},
URL查询对象 params:{ id: 12 },
查询对象序列化函数
paramsSerializer: function(params){ }
request body
data: { key: ‘aa’},
超时设置s timeout: 1000,
跨域是否带Token withCredentials: false,
自定义请求处理 adapter: function(resolve, reject, config){},
身份验证信息 auth: { uname: ‘’, pwd: ‘12’},
响应的数据格式 json / blob /document /arraybuffer / text / stream
responseType: ‘json’,
8.4 同时发送两个请求
使用axios.all, 可以放入多个请求的数组. axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2
import axios from 'axios'
export default {
name: 'app',
created () {
// 发送并发请求
axios.all([axios.get('tp://000.000/xxxx'),
axios.get('tp://000.000/xxxx', {params: {type: 'sell', page: 1}})])
.then(axios.spread((rea1, res2) => {
console.log(res1);
console.log(res2);
}))
}
}
8.5 axios实例
某些请求需要使用特定的baseURL或者timeout或者content-Type等.
const inatancel = axios.create({
baseURL: 'http: //111.2222',
timeout: 5000
})
instancel({
url: '/home/mutidata'
}).then(res => {
console.log(res);
})
instancel({
url: '/home/index'
}).then(res => {
console.log(res);
})
8.6 axios封装
建立一个network文件夹, 在里面建立一个request.js文件
export function request (config) {
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://111.222.33.44',
timeout: 5000
})
// 2. 发送真正的网络请求
// 返回的 instancel(config)就是一个promise
return instancel(config)
}
在使用的文件中. 封装request模块
import {request} from './network/request';
request({
url: '/home/multidata'
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
8.7 axios拦截器
axios提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理
// axios.config.ts
export function request (config) {
// 1. 创建axios的实例
const instance = axios.create({
baseURL: 'http://111.222.33.44',
timeout: 5000
...config
})
// 2. axios 请求拦截器
// config可以随便命名
instance.interceptors.request.use(config => {
// 在发送请求之前做些什么
// 不返回, 调用的时候会进入err
return config
}, err => { // 发送都没成功
console.log(err);
})
// 3. axios 响应拦截器
instance.interceptors.response.use(response=> {
// 对响应数据做点什么
console.log("response:", response);
const { code, data, message } = response.data;
if (code === 200) return data;
else if (code === 401) {
jumpLogin();
} else {
Message.error(message);
return Promise.reject(response.data);
}
},
);
// 4. 发送真正的网络请求
return instancel(config)
}
8.8 使用 cancel token 取消请求
可以使用 CancelToken.source
工厂方法创建 cancel token,像这样:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
还可以通过传递一个 executor
函数到 CancelToken
的构造函数来创建 cancel token
:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// cancel the request
cancel();
9. 捕获异步操作的异常
9.1 捕获Promise中的异常
在JavaScript中,你可以使用.catch()
方法来捕获Promise中的异常:
function asyncFunction() {
return new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
if (/* 一切正常 */) {
resolve('操作成功');
} else {
reject(new Error('发生错误'));
}
}, 1000);
});
}
asyncFunction()
.then(result => {
console.log(result);
})
.catch(error => {
console.error('捕获到异常:', error);
});
9.2 使用async/await
使用async/await
时,你可以像处理同步代码一样使用try/catch
来捕获异常:
async function asyncFunction() {
// 模拟异步操作
return new Promise((resolve, reject) => {
setTimeout(() => {
if (/* 一切正常 */) {
resolve('操作成功');
} else {
reject(new Error('发生错误'));
}
}, 1000);
});
}
async function main() {
try {
const result = await asyncFunction();
console.log(result);
} catch (error) {
console.error('捕获到异常:', error);
}
}
main();
async function doSomething() {
try {
const result = await someAsyncFunction();
// 处理结果
} catch (error) {
// 处理错误
console.error('An error occurred:', error);
}
}
doSomething();
练习题
setTimeout(() => {
console.log(1)
new Promise(resolve => {
setTimeout(() => {
console.log(5)
resolve(5);
}, 0)
console.log(6)
}).then(res => {
console.log(7)
});
new Promise(resolve => {
setTimeout(() => {
console.log(5)
resolve(5);
}, 0)
console.log(6)
}).then(res => {
console.log(7)
});
}, 0)