JavaScript 系列之:Ajax、Promise、Axios
前言
-
同步:会阻塞。同步代码按照编写的顺序逐行依次执行,只有当前的任务完成后,才会执行下一个任务。
-
异步:异步代码不会阻塞后续代码的执行。当遇到异步操作时,JavaScript 会将该操作放入任务队列中,继续执行后续的同步代码,直到同步代码执行完毕,再从任务队列中取出异步操作的结果进行处理。
Ajax、Promise、Axios 他们都是异步相关的技术。
-
Ajax 用来发起网络请求;
-
Promise 是 JavaScript 的一种异步编程解决方案(不仅限于异步网络请求),可以使用 Promise 来封装 Ajax 网络请求;
-
Axios 就是使用 Promise 来封装 Ajax 请求的。
Ajax
Ajax(Asynchronous Javascript And XML),翻译为异步的 Javascript 和 XML,它不是编程语言,是一项 Web 应用程序技术。能够在不重新加载整个页面的情况下更新部分网页内容。
Ajax 通过 XMLHttpRequest 对象来向服务器发出异步请求,从服务器获得数据,然后用 JavaScript 来操作 DOM 从而更新局部页面。
特点:
-
异步通信
-
发送请求后,程序并不会等待响应结果,而是会继续往下运行
-
所以,必须要在 Ajax 状态监听的回调函数中,才能保证获取响应数据
-
-
刷新数据而不会加载整个页面
-
不用 Ajax:更新或提交内容——需要重新加载整个网页
-
使用 Ajax:更新或提交内容——只更新部分网页
-
-
无需插件
- 使用纯粹的 JavaScript 和浏览器内置的 XmlHttpRequest 对象
缺点:
-
Ajax 不能使用 back 和 history 功能,即对浏览器机制的破坏。
-
安全问题:Ajax 暴露了与服务器交互的细节
XMLHttpRequest 对象
XMLHttpRequest 对象是由浏览器提供的,它是浏览器的内置对象,而不是 JavaScript 的内置对象。可以通过 window.XMLHttpRequest 得到。
常用的方法:
-
open(get/post, url, 是否异步默认true):创建 http 请求
-
send():发送请求给服务器
-
setRequestHeader():设置头信息(使用 post 才会用到,get 并不需要调用该方法)
-
onreadystatechange:用于监听 ajax 的工作状态(readyState 变化时会调用此方法)
常用的属性:
-
readyState:用来存放 XMLHttpRequest 的状态,监听从0-4发生不同的变化
-
0: 还未创建请求,即未调用 open() 方法
-
1: 已调用 open() 方法,但未发送 send() 方法
-
2: 已调用 send() 方法,但未接收到响应
-
3: 已接收到部分响应
-
4: 已接收到全部的响应
-
-
status:服务器返回的状态码
-
responseText:服务器返回的文本内容
Ajax 如何解决浏览器缓存问题
-
在 Ajax 发送请求前加上
anyAjaxObj.setRequestHeader("If-Modified-Since", "0")
-
在 Ajax 发送请求前加上
anyAjaxObj.setRequestHeader("Cache-Control", "no-cache")
-
在 URL 后面加上一个随机数:
"fresh=" + Math.random()
-
在 URL 后面加上时间戳:
"nowtime=" + new Date().getTime()
手写 Ajax
Ajax 的基本流程:创建 XMLHttpRequest 对象 => 发送数据 => 接收数据
发送 get 请求代码示例:
// 创建一个新的 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();// 配置 GET 请求,URL 后面加上参数
xhr.open('GET', 'https://example.com/api?name=John&age=30', true);// 设置请求头 (可选)
xhr.setRequestHeader('Content-Type', 'application/json');// 监听请求完成后的回调
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {console.log('GET 请求成功:', xhr.responseText);} else {console.log('GET 请求失败:', xhr.status);}
};// 发送请求
xhr.send();
发送 post 请求代码示例:
// 创建一个新的 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();// 配置 POST 请求
xhr.open('POST', 'https://example.com/api', true);// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');// 请求完成后的回调
xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {console.log('POST 请求成功:', xhr.responseText);} else {console.log('POST 请求失败:', xhr.status);}
};// 准备 POST 请求数据
var data = {name: 'John',age: 30
};// 发送 POST 请求,并传递数据
xhr.send(JSON.stringify(data));
Promise
ES6 新特性,Promise 是 JavaScript 中用于处理异步操作的一种方案。本质是一个构造函数,参数是一个执行器函数。
// 创建实例
var promise = new Promise((resolve, reject)=> {console.log(1)// 异步操作setTimeout(() => {const success = true; // 模拟异步操作成功if (success) {console.log(2)resolve('操作成功');} else {reject('操作失败');}}, 2000);
}).then(res =>{// 成功时的回调函数,即 resolve 被调用时执行。console.log(3, res) // 3 操作成功
}).catch(err =>{// 失败时的回调函数,即 reject 被调用时执行。
});// 立即输出 1,等待两秒钟后输出 2,再立即输出 3
初学者可能不太理解这段代码,我们将它拆分开:
// 定义一个执行异步操作的函数
function executorFunction(successMethod, errorMethod) { console.log(1); // 异步操作前的日志 // 使用 setTimeout 模拟异步操作 setTimeout(() => { const success = true; // 模拟异步操作成功 if (success) { console.log(2); // 异步操作成功时的日志 successMethod('操作成功'); // 调用 successMethod,表示操作成功 } else { errorMethod('操作失败'); // 调用 errorMethod,表示操作失败 } }, 2000); // 模拟异步操作耗时 2 秒
} // 定义成功时的回调函数
function onSuccess(res) { console.log(3); // 异步操作成功后的日志 console.log(res); // 打印 successMethod 传递的值
} // 定义失败时的回调函数
function onError(err) { console.log('发生错误:', err); // 打印 errorMethod 传递的错误信息
} // 使用构造函数创建 Promise 对象,构造函数接受一个执行器函数作为参数
var promise = new Promise(executorFunction); // 使用 .then() 和 .catch() 链接 Promise
promise.then(onSuccess).catch(onError);
在上面的示例中,function executorFunction(successMethod, errorMethod)
就是执行器函数,执行器函数在创建 Promise 实例时立即被调用,此时会将 PromiseState(即 Promise 的状态值) 初始化为 pending
。
执行器函数接受两个回调函数作为参数,调用第一个参数时表示成功,会将 PromiseState 改为 fulfilled
,并将成功的结果传递给后续的 then 方法;调用二个参数时表示失败,会将 PromiseState 改为 rejected
,并将错误的结果传递给后续的 catch 方法。
什么是执行器函数?
执行器函数就是那些被设计来执行特定任务或操作的函数,它与普通函数并没有什么区别,只是在叫法上更加细致。
为什么要有执行器函数?为什么不直接使用 resolve 和 reject 作为参数?
其实就是为了封装和控制。其实执行器函数内部封装了很多隐藏的方法。例如 Promise 的状态转换,是怎么从 pending 转为 fulfilled 或 rejected 的呢?还有调用 resolve('操作成功')
方法时,Promise 链是怎么拿到结果的呢?等等这些都是执行器函数在发挥作用。没有执行器函数是无法做到这些的。
为什么要在 .then() 里操作成功时的回调函数,而不是直接在 resolve 方法之前操作?
这是一个强烈的建议,但并不是一个唯一标准。
-
异步性:Promise 的主要目的是处理异步操作。如果你在 resolve 之前直接进行操作,那么这些操作就会是同步的,而不是异步的。
-
链式调用:Promise 支持链式调用,允许你通过
.then()
方法将多个异步操作连接在一起,如果你在 resolve 之前直接进行操作,这种链式调用就无法实现。 -
错误处理:Promise 通过
.catch()
方法提供了集中的错误处理机制。如果你在 resolve 之前直接进行操作,并且这些操作可能抛出错误,那么你就需要使用传统的 try-catch 语句来捕获这些错误。 -
解耦:将操作放在
.then()
中可以将这些操作与 Promise 的创建和解析解耦。
Promise 特点
-
对象的状态不受外界影响
-
这个好理解,每一个 Promise 对象都是通过构造函数 new 出来的,状态只在该构造函数内部有效
-
有三种状态:pending(等待状态)、fulfilled(成功/已解决状态)、rejected(失败/被拒绝状态)
-
-
有 PromiseState 属性和 PromiseResult 属性
-
PromiseState 值是当前状态值(pending、fulfilled、rejected 中的一种),
-
PromiseResult 值是当前结果值(resolve 或 reject 中的值)。
-
-
一旦状态改变,就不会再变
let p = new Promise((rosolve, reject)=> {rosolve('成功') // 第一次状态改变,一旦状态被改变,后面的代码就都不执行了reject('失败') // 再改变无效consoloe.log('我不会被执行') }) console.log(p)
Promise 的 .then() 和 .catch()
-
.then()
接收 1-2 个回调函数作为参数:-
第一个参数是当 Promise 成功(fulfilled)时调用的函数。
-
第二个参数可选,是当 Promise 失败(rejected)时调用的函数。
-
-
.catch()
接收 1 个回调函数作为参数-
参数是当 Promise 失败(rejected)时调用的函数。
-
如果
.then()
定义了第二个参数,那么当 Promise 被拒绝时会调用 then 的第二个参数函数,而不是 catch 中的参数函数
-
let p1 = new Promise((rosolve, reject) => {setTimeout(() => {throw new Error('出错了'); })
})
.then(data => {console.log('then data:', data);
})
.catch(error => {console.error('catch error:', error);
});
// 没有调用 rosolve 或 reject,then 和 catch 不执行,哪怕是抛出异常也不会执行。
// .then() 接收一个参数
let p1 = new Promise((rosolve, reject) => {setTimeout(() => {rosolve('成功') // rosolve})
})
.then(data => {console.log('then data:', data); // then data: 成功
})
.catch(error => {console.error('catch error:', error);
});let p2 = new Promise((rosolve, reject) => {setTimeout(() => {reject('失败') // reject})
})
.then(data => {console.log('then data:', data);
})
.catch(error => {console.error('catch error:', error); // catch error: 失败
});
// .then() 接收两个参数
let p = new Promise((rosolve, reject) => {setTimeout(() => {reject('失败') // reject})
})
.then(data => {console.log('then data:', data);},error => {console.error('then error:', error); // then error: 失败}
)
.catch(error => {console.error('catch error:', error);
});
既然 then 方法的第二个参数也可以用于处理 reject 的情况,那为什么还要有 catch 方法呢?为什么要有两个重复的功能?
从下面要介绍的 Promise 链式调用中你会找到答案。
Promise 链式调用
链式调用的原理
-
then 方法接收两个可选的回调函数作为参数,这两个参数是有返回值的,它们的返回值被用作下一个 then 方法的参数(如果不定义返回值就是 return undefined)
-
then 方法的返回值其实是一个 Promise 对象,如果 then 中 return 的是一个非 Promise 类型的值(如字符串、undefined 等),就会自动将这个值包裹成一个 PromiseStatus 为 fulfilled、PromiseResult 为这个值的 Promise 对象。例:return 1 会变成 return Promise.resolve(1)。
// then 方法的返回值其实是一个 Promise 实例
const p = new Promise((rosolve, reject) => {setTimeout(() => {rosolve('成功') // rosolve}, 2000)
})const p1 = p.then(data => {console.log('then 1 data:', data);return 1},error => {console.error('then 1 error:', error);}
)console.log('p1:', p1);/*
then 1 data: 成功
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 1
*/
// 返回值被用作下一个 then 方法的参数
const p = new Promise((rosolve, reject) => {setTimeout(() => {rosolve('成功') // rosolve}, 1000)
})p.then(data => {console.log('then 1 data:', data); // then 1 data: 成功return 1},error => {console.error('then 1 error:', error);}
)
.then(data => {console.log('then 2 data:', data); // then 2 data: 1
});
链式调用的规则
1、如果在上一个 then 或 catch 中捕获了 reject,那么最终都会被下一个 then 的成功回调函数捕获:
// 情况 1:then 的失败回调捕获 reject
const p = new Promise((rosolve, reject) => {setTimeout(() => {reject('失败') // reject}, 1000)
})p.then(data => {console.log('then 1 data:', data);return 1},error => {// then 的失败回调捕获 rejectconsole.error('then 1 error:', error); // then 1 error: 失败return 2}
)
.then(data => {console.log('then 2 data:', data); // then 2 data: 2},error => {console.error('then 2 error:', error);}
);
// 情况 2:catch 捕获 reject
const p = new Promise((rosolve, reject) => {setTimeout(() => {reject('失败') // reject}, 1000)
})p.then(data => {console.log('then 1 data:', data);return 1},
)
.catch(error=> {// catch 捕获 rejectconsole.error('error 1 error:', error); // error 1 error: 失败return 2
})
.then(data => {console.log('then 2 data:', data); // then 2 data: 2},error => {console.error('then 2 error:', error);}
);
2、如果在上一个 then 或 catch 中没有捕获 reject,或 return 了一个 reject,或抛出了异常,那么最终会被下一个 then 的失败回调函数或下一个 catch 捕获:
// 情况 1:没有捕获 reject
const p = new Promise((rosolve, reject) => {setTimeout(() => {reject('失败') // reject}, 1000)
})p.then(data => {console.log('then 1 data:', data);return 1},// error 中没有捕获 reject,代码屏蔽了//error => {// console.error('then 1 error:', error);// return 2//}
)
// catch 也没有捕获 reject,代码屏蔽了
//.catch(error=> {
// console.error('error 1 error:', error);
// return 2
//})
.then(data => {console.log('then 2 data:', data);},// 会被第二个 then 的 失败回调捕获error => {console.error('then 2 error:', error); // then 2 error: 失败}
);
// 或者被第二个 catch 捕获
//.catch(error => {
// console.error('catch 2 error:', error); // catch 2 error: 失败
//})
// 情况 2:return 了 reject
const p = new Promise((rosolve, reject) => {setTimeout(() => {rosolve('成功') // rosolve//reject('失败') // reject}, 1000)
})p.then(data => {console.log('then 1 data:', data); // then 1 data: 成功return new Promise((rosolve, reject)=> {setTimeout(()=> {reject('失败 2') // reject})})},// 无论是在成功回调还是失败回调中使用 return reject//error => {// console.error('then 1 error:', error); // then 1 error: 失败// return new Promise((rosolve, reject)=> {// setTimeout(()=> {// reject('失败 2') // reject// })// })//}
)
.then(data => {console.log('then 2 data:', data);},// 会被第二个 then 的 失败回调捕获error => {console.error('then 2 error:', error); // then 2 error: 失败 2}
)
// 或者被 catch 捕获
//.catch(error => {
// console.error('catch 2 error:', error); // catch 2 error: 失败 2
//})
// 情况 3:抛出异常
const p = new Promise((rosolve, reject) => {setTimeout(() => {rosolve('成功') // rosolve}, 1000)
})p.then(data => {console.log('then 1 data:', data); // then 1 data: 成功throw new Error('抛出异常')}
)
.then(data => {console.log('then 2 data:', data);},// 会被第二个 then 的 失败回调捕获error => {console.error('then 2 error:', error); // then 2 error: 抛出异常}
)
// 或者被 catch 捕获
//.catch(error => {
// console.error('catch 2 error:', error); // catch 2 error: 抛出异常
//})
链式调用的作用
1、可以按顺序执行多个异步操作。例如先获取用户信息,再根据用户信息获取部门 ID,再根据部门 ID 获取部门信息…
const p = new Promise((resolve, reject) => { // 获取用户信息 setTimeout(() => { const userInfo = { name: '张三', userId: '1' }; resolve(userInfo); }, 1000);
});p.then(data => { console.log('then 1 data:', data); // {name: '张三', userId: '1'}// 根据用户信息获取部门 ID return new Promise((resolve) => { setTimeout(() => { const departId = 1; resolve(departId); // 将 departId 传递给下一个 then }, 1000); });
})
.then(data => { console.log('then 2 data:', data); // 1// 根据部门 ID 获取部门信息 return new Promise((resolve) => { setTimeout(() => { const departInfo = { departId: data,departName: '人事部' }; resolve(departInfo); // 将 departInfo 传递给下一个 then }, 1000); });
})
.then(data => { console.log('then 3 data:', data); // {departId: 1, departName: '人事部'}
});
上面的例子为什么要在 then 中 return new Promise 而不是在 setTimeout 内部中 return?
因为 setTimeout
本身是一个异步函数,它返回的是一个定时器 ID,而不是你函数内部 return
的值。因此,在 setTimeout
中返回的值并不会成为 Promise
链中的下一个值。
2、集中错误处理。在 Promise 链中,任何一步的 .then()
或 .catch()
方法都可以捕获并处理错误。这意味着你可以在整个链中只使用一个 .catch()
来捕获和处理所有之前的异步操作中可能出现的错误,这大大简化了错误处理逻辑。
const p = new Promise((rosolve, reject) => {setTimeout(() => {rosolve('成功') // rosolve}, 1000)
})p.then(data => {console.log('then 1 data:', data); // then 1 data: 成功return 1
})
.then(data => {console.log('then 2 data:', data); // then 2 data: 1throw new Error('抛出异常')return 2
})
.then(data => {console.log('then 3 data:', data); // 没有执行return 3
})
.catch(error => {console.log('catch 3 error:', error); // catch 3 error: Error: 抛出异常
})
现在再来看看开始的那个问题:
“既然 then 方法的第二个参数也可以用于处理 reject 的情况,那为什么还要有 catch 方法呢?为什么要有两个重复的功能?”
-
catch 使代码结构更加清晰,可以很直观的知道这部分代码是专门用于处理错误的,而不用去 then 中找失败回调,它使得链式调用的流程非常流畅
-
then 的失败回调也并非毫无用处,它可以做到局部处理错误且不影响后续的链的运行,也可以根据拿到的具体值,做条件判断,再决定是否要抛出错误。
Promise 静态方法
Promise.resolve(value)
这个方法接收一个值(value
)作为参数,并返回一个 Promise 对象。
-
如果
value
本身就是一个 Promise 对象,那么返回的 Promise 会“跟随”这个value
Promise 的状态; -
如果
value
不是一个 Promise 对象,那么返回的 Promise 会以这个value
作为成功的结果(即 fulfilled 状态)。
// 传递 Promise 成功对象
const promise = new Promise(resolve => resolve('成功'));
Promise.resolve(promise).then(data => {console.log(data); // 成功
});-----// 传递 Promise 失败对象
const promise = new Promise((resolve, reject)=> {reject('失败')
});
Promise.resolve(promise).then(data => {},// 或者用 catcherror => {console.log("error:", error, typeof error); // error:失败 string}
);-----// 传递普通值
Promise.resolve(1).then(data => {console.log(data); // 1
});
Promise.reject(reason)
这个方法接收一个原因(reason
)作为参数,并返回一个已经处于 rejected 状态的 Promise 对象。这个 reason
会被封装在返回的 Promise 的拒绝原因中。
Promise.reject 与 Promise.resolve 的区别是:
-
Promise.reject 只能返回 rejected 状态的 Promise 对象,而 Promise.resolve 可以返回任意状态的 Promise 对象
-
Promise.reject 的参数就是拒绝原因,而 Promise.resolve 的参数可以理解成是一个 Promise 对象,它包括 PromiseStatus 和 PromiseResult,并以此决定 Promise.resolve 返回的 Promise 对象的状态和结果值。
Promise.reject('失败').catch(data => {console.log(data); // 失败
});-----const promise = new Promise((resolve, reject)=> {reject(1)
});// 这里,Promise.resolve(promise) 接收了一个已经 rejected 的 Promise 对象
// 因此,返回的 Promise 也会是 rejected 的,并且传递相同的拒绝原因(数字 1)
Promise.resolve(promise).then(data => {},error => {console.log("error:", error, typeof error); // 1, number}
);// Promise.reject(reason) 的设计初衷是接收一个拒绝原因(reason),
// 并立即返回一个处于 rejected 状态的 Promise。
// 这里的 reason 应该是任何非 Promise 的值(如字符串、数字、对象等)。
// 虽然技术上可以传入一个 Promise 对象作为 reason,
// 但这并不是 Promise.reject() 的预期用法,也不是一个好的实践。
Promise.reject(promise).then(data => {},error => {console.log("error:", error, typeof error); // 1, object}
);
Promise.all(iterable)
这个方法接受一个可迭代对象(通常是一个数组),其中的每一项都是一个 Promise 对象。
它返回一个新的 Promise 对象,这个 Promise 对象在所有输入的 Promise 对象都成功时才会成功,并且它的结果是一个数组,包含所有输入 Promise 对象的结果;如果其中一个 Promise 对象失败,那么这个新的 Promise 对象会立即失败,并返回那个失败的原因。
const p1 = Promise.resolve(3);
const p2 = 42;
const p3 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'foo'));Promise.all([p1, p2, p3]).then(values => {console.log('values:', values); // [3, 42, 'foo']
}).catch(error => {console.log('error:', error);
});
const p1 = Promise.resolve(3);
const p2 = 42;
const p3 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));Promise.all([p1, p2, p3]).then(values => {console.log('values:', values);
}).catch(error => {console.log('error:', error); // foo
});
拓展:setTimeout(function[, delay, arg1, arg2, ...])
;
一旦定时器到期,setTimeout 的第三个及以后的参数会作为第一个 function() 的参数传进去。
所以上面的例子实际上就是 setTimeout(() => {resolve('foo')}, 100)
Promise.all() 在处理多个并发请求的时候非常有效。
这里经常会有一个面试题:如何控制 JavaScript 并发一百个请求?
all、race、any、allSettled
与 all 类似的还有 race、any、allSettled,他们都接收一个数组,数组中的每一项都是一个 Promise 对象,区别如下:
-
all:只有数组中的 Promise 都成功时才进入 then,有一个失败则进入 catch
-
race:数组中最先完成的 Promise(不是按参数顺序),成功则进入 then,失败则进入 catch
-
any:数组中最先成功的 Promise,其 resolve 的内容会进入 then,所有的 Promise 都失败则进入 catch
-
allSettled:数组中所有的 Promise 完成时(无论失败还是成功),会进入 then,永远不会进入 catch
Promise 和 async/await
async
async 用于将一个函数定义为异步函数,且该异步函数的返回值是一个 Promise 对象
const fn = async function() {console.log(1)
}
const p = fn()
console.log(p, p instanceof Promise)
/*
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
true
*/
const fn = async function() {console.log(1) // 1return 2
}
const p = fn()
console.log(p, p instanceof Promise)
/*
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 2
true
*/
所以 async 可以不需要 await:
const fn = async function() {console.log(1) // 1return 2
};
const p = fn(); // p 是一个 promise 对象
p.then(res => {console.log(res) // 2
});
但是要注意下面这种情况是错误的示范:
const fn = async function() {setTimeout(() => {console.log(1)return 2})
}
const p = fn()
console.log(p, p instanceof Promise)
p.then(res => {console.log('res:', res) // undefined
})
为什么这里的 res 打印的是 undefined?这个问题其实在上面的【链式调用的作用】中有说到过:
因为 setTimeout
本身是一个异步函数,它返回的是一个定时器 ID,而不是你函数内部 return
的值。因此,在 setTimeout
中返回的值并不会成为 Promise
链中的下一个值。
await
await 的作用是等待一个 Promise 对象解决(resolve)或拒绝(reject),它会暂停 await 后面的代码的执行。
-
当 await 等待的 Promise 被解决,会返回解决值
-
当 await 等待的 Promise 被拒绝,会抛出异常
const fn = async function() {console.log(1)return 2
};async function test() {const result = await fn() // 返回解决值console.log("result:", result)
}
console.log("主线程执行中...")
test()/*
主线程执行中...
1
result:2
*/
const p = Promise.reject('失败')async function test() {try {const result = await pconsole.log("result:", result) // 不执行} catch (error) {console.log('异常:', error);}
}
console.log("主线程执行中...")
test()/*
主线程执行中...
异常: 失败
*/
如果 await 等待一个非 Promise 对象会怎样?
async function test() {const result = await 'str'console.log("result:", result)
}
console.log("主线程执行中...")
test()/*
主线程执行中...
result: str
*/
答案:JavaScript 会尝试将该值隐式地转换为一个已解决的 Promise 对象。
为什么 await 一定要在 async 中?
-
语法限制,单独使用 await 会报错
-
await 的作用是等待一个 Promise 对象被解决或拒绝,而 async 函数会隐式地返回一个 Promise 对象
async/await
为什么说 async/await 可以简化 Promise 呢?我们还是使用之前【链式调用的作用】中的一个例子;
先获取用户信息,再根据用户信息获取部门 ID,再根据部门 ID 获取部门信息…
使用 Promise:
const p = new Promise((resolve, reject) => { // 获取用户信息 setTimeout(() => { const userInfo = { name: '张三', userId: '1' }; resolve(userInfo); }, 1000);
});p.then(data => { console.log('then 1 data:', data); // {name: '张三', userId: '1'}// 根据用户信息获取部门 ID return new Promise((resolve) => { setTimeout(() => { const departId = 1; resolve(departId); // 将 departId 传递给下一个 then }, 1000); });
})
.then(data => { console.log('then 2 data:', data); // 1// 根据部门 ID 获取部门信息 return new Promise((resolve) => { setTimeout(() => { const departInfo = { departId: data,departName: '人事部' }; resolve(departInfo); // 将 departInfo 传递给下一个 then }, 1000); });
})
.then(data => { console.log('then 3 data:', data); // {departId: 1, departName: '人事部'}
});
使用 async/await:
async function getUserInfo() {return new Promise((resolve, reject) => {setTimeout(() => {const userInfo = {name: '张三', userId: '1' };resolve(userInfo);}, 1000);});
}async function getDepartId(userInfo) {return new Promise((resolve, reject) => {setTimeout(() => {let departIdif(userInfo.userId == 1) {departId = 1;} resolve(departId);}, 1000);});
}async function getDepartInfo(departId) {return new Promise((resolve, reject) => {setTimeout(() => {let departInfoif(departId == 1) {departInfo = { departId: departId,departName: '人事部' };} resolve(departInfo);}, 1000);});
}async function test() { try { const userInfo = await getUserInfo(); console.log('用户信息:', userInfo);const departId = await getDepartId(userInfo);console.log('部门 ID:', departId);const departInfo = await getDepartInfo(departId);console.log('部门信息:', departInfo);} catch (error) { console.error('发生错误:', error); }
} test()/*
用户信息: {name: '张三', userId: '1'}
部门 ID: 1
部门信息: {departId: 1, departName: '人事部'}
*/
自定义封装 Promise 函数
有助于理解 Promise 原理,可以不看。
class Promise {constructor(executor) {this.PromiseState = 'pending'this.PromiseResult = nullthis.callback = [] //then的参数值容器const that = thisfunction resolve(data) {//保证状态值只能改一次if (that.PromiseState !== 'pending') return//设置状态值为成功that.PromiseState = 'fullfilled'//设置promise结果值that.PromiseResult = data//实现链式调用setTimeout(() => {that.callback.forEach(item => {item.onResolve(data)})})}function reject(data) {if (that.PromiseState !== 'pending') return//设置状态值为失败that.PromiseState = 'rejected'//设置promise结果值that.PromiseResult = datasetTimeout(() => {that.callback.forEach(item => {item.onReject(data)})})}//设置通过throw 改变状态值try {//立即执行保证同步调用(执行器函数)executor(resolve, reject);} catch (e) {reject(e) //e为throw ‘error’ 抛出异常时的值}}//指定原型then方法then(onResolve, onReject) {const that = this//判断回调函数参数 设置参数默认值if (typeof onReject !== 'function') {onReject = reason => {throw reason}}if (typeof onResolve !== 'function') {onResolve = value => value}return new Promise((resolve, reject) => {//封装重复函数function callback(type) {try {let result = type(that.PromiseResult)if (result instanceof Promise) {//if返回对象为promiseresult.then(v => {resolve(v)}, r => {reject(r)})} else {//设置返回值的promise对象成功值resolve(result)}} catch (e) {reject(e) //当抛出异常}}//console.log(this)// 运用箭头函数的原因 这里的this指向函数定义的代码块//设置成功的回调执行if (this.PromiseState === 'fullfilled') {//保证then方法内回调为异步执行setTimeout(() => {callback(onResolve)})}//设置失败回调执行if (this.PromiseState === 'rejected') {setTimeout(() => {callback(onReject)})}//设置改变状态后再执行then回调(对于回调方式改变状态值)if (this.PromiseState === 'pending') {this.callback.push({onResolve: function() { //处理promise异步修改对象状态(实现then回调函数执行在异步回调之后)callback(onResolve)},onReject: function() {callback(onReject)}})}})}//添加catch方法catch (onreject) {return this.then(undefined, onreject)}//给构造函数promise封装resolve 和reject方法static resolve(value) {return new Promise((resolve, reject) => {if (value instanceof Promise) {value.then(v => {resolve(v)}, r => {reject(r)})} else {resolve(value)}})}static reject(reason) {return new Promise((resolve, reject) => {reject(reason)})}//封装all方法static all(values) {return new Promise((resolve, reject) => {let count = 0let arr = []for (let i = 0; i < values.length; i++) {values[i].then(v => {count++arr[i] = vif (count === values.length) {resolve(arr)}}, r => {reject(r)})}})}//封装race方法static race(values) {return new Promise((resolve, reject) => {for (let i = 0; i < values.length; i++) {values[i].then(v => {resolve(v)}, r => {reject(r)})}})}}
Axios
Axios 是一个基于 Promise 的用于发送 HTTP 请求的客户端库。它会返回一个 Promise 对象,代表 HTTP 请求的结果。
并且 Axios 还可以支持请求和响应拦截器、请求取消、HTTP 方法别名、自动转换 JSON 数据等高级功能。
示例:
// get 请求
axios({url: 'http://example.com/resource',method: 'get',params: {id: 1,name: '张三'},headers: {'Content-Type': 'application/json'}
})
.then(response => {console.log(response);
})
.catch(error => {console.error(error);
});// post 请求
axios({url: 'http://example.com/resource',method: 'post',// 使用 data 接收参数data: {id: 1,name: '张三'},headers: {'Content-Type': 'application/json'}
})
.then(response => {console.log(response);
})
.catch(error => {console.error(error);
});
Axios 特点和优势
特点:
-
从浏览器中创建 XMLHttpRequests
- Axios 的浏览器 API 能够发送 XMLHttpRequests,这使得它能够从浏览器中以异步的方式与服务器通信。
-
从 node.js 中创建 HTTP 请求
- 在服务器端,Axios 提供了相似的 API 通过 HTTP 方式与其他服务进行交互。
-
支持 Promise API
- 使用 Promise API 实现请求和响应的同时处理,让异步代码变得简洁,并方便了错误处理。
-
拦截请求和响应
- Axios 的拦截器让你可以在请求发送到服务器前或服务器响应返回应用前更容易地处理它们。
-
转换请求和响应的数据
- 自动转换请求数据和响应数据为 JSON 格式。
-
取消请求
- 提供了通信接口来取消请求,避免不必要的网络活动。
-
统一错误处理
- Axios 使得错误管理和处理集中化,可以对所有 HTTP 请求统一处理异常。
Axios 与 Fetch 的区别
Fetch 示例:
// get 请求
fetch(`http://example.com/resource?id=1&name=张三`, {method: 'GET',headers: {'Content-Type': 'application/json'}
})
.then(response => {// 解析 JSON 响应return response.json();
})
.then(data => {// 处理数据console.log(data);
})
.catch(error => {// 错误处理console.error('Fetch error:', error);
});// post 请求:
fetch(`http://example.com/resource`, {method: 'GET',headers: {'Content-Type': 'application/json'},// 使用 body 接收参数body: JSON.stringify({id: 1,name: '张三'})
})
.then(response => {// 解析 JSON 响应return response.json();
})
.then(data => {// 处理数据console.log(data);
})
.catch(error => {// 错误处理console.error('Fetch error:', error);
});
比较:
-
都支持链式调用,都是基于 Promise。
-
原生
-
Axios 是一个第三方库
-
Fetch 是一个原生的 JavaScript API,即不需要引用
-
-
拦截处理
-
Axios 能够拦截请求和响应。
-
Fetch 不支持请求和响应的拦截。
-
-
取消请求
-
Axios 使用 CancelToken 取消请求。
-
Fetch 使用 AbortController 取消请求。
-
-
错误处理
-
Axios 将任何状态码 >= 200 和 < 300 的响应视为有效响应,其他的都将被拒绝。
-
Fetch 只有在网络故障时或请求被阻止时才会拒绝 promise;如果 HTTP 状态码为 404 或 500,它会将 promise 的状态标记为 resolve,并且需要开发者进行额外检查。
-
-
JSON 数据处理
-
Axios 会自动将请求和响应转为 JSON 格式。
-
Fetch 需要手动将请求转为 JSON 字符串,手动将响应转为 JSON
-
-
处理 Cookie
跨域请求中 Axios 和 Fetch 默认都不会携带 cookie,需要手动设置:
-
Axios 设置
withCredentials: true
。 -
Fetch 设置
credentials: 'include'
。
-
Axios 响应结构
字段 | 类型 | 描述 |
---|---|---|
data | any | 服务器响应的数据。Axios 默认会尝试将此数据转换为 JSON 对象 |
status | number | HTTP 响应的状态码,如 200 表示成功,404 表示未找到等 |
statusText | string | HTTP 状态码的文本信息,如 OK 或 Not Found |
headers | Object | 响应头。一个包含所有响应头的对象,键名为响应头的名称,键值为响应头的值。 |
config | Object | 请求时使用的所有配置选项 |
request | XMLHttpRequest | 生成当前响应的请求对象,在浏览器中,它是 XMLHttpRequest 实例(几乎不用) |
你可以通过在 .then() 或者 async/await 捕获响应结果来访问这些字段。例如:
axios.get('/some-url').then(response => {console.log(response.status); // 例如:200console.log(response.data); // 服务器端返回的实际数据});async function fetchData() {const response = await axios.get('/some-url');console.log(response.status);console.log(response.data);
}
Axios 请求拦截器(Interceptors)
Axios 拦截器允许开发者在请求被发送到服务器和响应到达客户端之前,对它们进行处理和修改。
请求拦截器的作用
-
添加公共头部。
-
请求数据序列化:在请求发送到服务器之前对请求数据进行处理或转换。
-
设置条件请求:根据特定的条件或业务逻辑取消或更改即将发送的请求。
-
日志记录:记录即将发送的请求的详细信息,方便调试。
-
设置通用参数:例如,添加时间戳参数以防止 GET 请求被浏览器缓存。
响应拦截器的作用
-
统一错误处理:可以在拦截器中捕捉到错误响应,并进行统一的错误处理。例如,根据 HTTP 状态码显示错误信息。
-
数据转换:将响应数据从 JSON 转换为其他格式,或者在数据被传递给 then 或 catch 方法之前,进行预处理。
-
自动刷新 Token:当收到 token 过期的响应时,可以发送请求来刷新 token,然后重试原始请求。
-
性能监控:可以计算请求响应时间,用于应用程序的性能监控。
拦截器例子:
// 添加请求拦截器
axios.interceptors.request.use(function (config) {// 在发送请求之前做些什么config.headers.common['Authorization'] = `Bearer ${token}`;return config;
}, function (error) {// 对请求错误做些什么return Promise.reject(error);
});// 添加响应拦截器
axios.interceptors.response.use(function (response) {// 对响应数据做点什么return response;
}, function (error) {// 对响应错误做点什么if (error.response && error.response.status === 401) {// 处理授权错误}return Promise.reject(error);
});
return Promise.reject
的作用就是中断 Promise 链中的后续 .then()
方法调用,并允许 .catch()
方法捕获到这个错误。
但是要注意 return Promise.reject
并不会中断 HTTP 请求的发送和接收。例如一个 HTTP 请求刚进入 axios.interceptors.request
的时候就立即使用 Promise.reject
,那么这个 HTTP 请求还是会被发送到服务器。这与 CancelToken 不同,CancelToken 会取消该请求的发送。
Axios 如何取消请求
使用 CancelToken.source 工厂方法创建取消令牌:
// 1. 引入 axios
const axios = require('axios');// 2. 创建一个取消令牌源
const CancelToken = axios.CancelToken;
const source = CancelToken.source();// 3. 发起请求
axios.get('https://jsonplaceholder.typicode.com/posts', {// 4. 将取消令牌作为请求配置的一部分传递cancelToken: source.token
})// then 中处理请求成功的情况.then(response => {console.log('请求成功:', response.data);})// catch 中处理错误和取消的情况.catch(error => {// 6. 处理请求错误或取消的情况if (axios.isCancel(error)) {console.log('请求取消', error.message);} else {console.error('请求错误', error);}});// 5. 在某个条件下取消请求
// 在 1 秒后取消请求
setTimeout(() => {source.cancel('取消原因');
}, 1000);
总结实现步骤:
-
创建取消令牌源
const CancelToken = axios.CancelToken; const source = CancelToken.source();
-
发起请求时将取消令牌作为请求配置的一部分传递
{cancelToken: source.token,... }
-
在某个条件下使用
source.cancel()
取消请求source.cancel('取消原因')
; -
在 catch() 中使用
axios.isCancel()
处理被取消的请求(非必须).catch(error => { if (axios.isCancel(error)) { console.log('请求被取消:', error.message); }... });
Axios 默认配置
如果想为所有的请求统一配置某些设置,可以使用默认配置 axios.defaults:
// 设置全局的 baseURL 默认值
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';// 设置全局的 headers 默认值
axios.defaults.headers.common['Authorization'] = 'Bearer your.token.here';// 设置全局的 timeout 默认值
axios.defaults.timeout = 2500;
Axios 中如何创建实例(instance)
创建实例的目的是允许你为该实例的所有请求预定义一些配置项,比如基础 URL、请求头、超时时间等。这样做的好处是在同一个应用中可以重用这一配置,避免每次发送请求时都需要设置相同的配置项。
要创建 Axios 的新实例,你可以使用 axios.create() 方法并传递一个配置对象。以下是创建实例的示例:
// 创建实例
const axiosInstance = axios.create({baseURL: 'https://api.example.com', // 所有请求的基础 URLtimeout: 1000, // 全部请求的超时时间headers: {'X-Custom-Header': 'foobar'} // 全局自定义的请求头
});// 使用实例发起 GET 请求
axiosInstance.get('/users').then(response => {console.log(response.data);});// 使用实例发起 POST 请求
axiosInstance.post('/users', {data: {username: 'example'}
}).then(response => {console.log(response.data);});// 每个实例都能够单独配置拦截器
// 添加请求拦截器
axiosInstance.interceptors.request.use(config => {config.headers['Authorization'] = 'Bearer token'; // 为即将发出的请求动态设置授权头部return config;
});// 添加响应拦截器
axiosInstance.interceptors.response.use(response => {// 处理响应数据return response;
}, error => {// 处理响应错误return Promise.reject(error);
});
默认配置和实例配置的区别:
区别 | 默认配置 | 实例配置 |
---|---|---|
作用范围 | 全局,对所有请求生效 | 局部,只对当前实例的请求生效 |
配置方式 | 直接修改 axios.defaults | 使用 axios.create() 创建新的实例并配置 |
适用场景 | 统一配置所有请求 | 针对不同的请求需要不同配置时使用 |
Axios 性能优化
-
使用缓存避免不必要的请求
-
并发请求
-
使用请求和响应拦截器优化错误处理
-
取消重复请求
-
压缩数据:确保服务器能发送压缩的响应数据(如使用 gzip 或 br 压缩)。这减少了传输的数据量,从而提高了性能。
-
使用 HTTP2:如果服务器支持 HTTP2,使用 HTTP2 可以显著提高性能,因为它提供了头部压缩、多路复用等优势。你可以考虑选择或升级到支持 HTTP2 的服务器。
Axios 处理大型 JSON 数据
设置 responseType: 'stream'
表示获取一个可读的流(stream),然后你可以逐步读取和解析这个流,而不是一次性将整个响应体加载到内存中。这对于大型文件特别有用。
流是原始的二进制数据。你需要自己实现逻辑来将流中的数据转换为 JSON。
axios({method: 'get',url: 'http://example.com/large.json',responseType: 'stream'
})
.then((response) => {// 处理流式响应数据
});
Axios 完整封装示例
// request.js
import axios from 'axios';// 创建一个 axios 实例
const service = axios.create({baseURL: process.env.VUE_APP_BASE_API, // 基础路径timeout: 5000 // 请求超时时间
});// 存储正在进行的请求
const pendingRequests = new Map();
const requestTimestamp = new Map(); // 存储请求的时间戳// 请求拦截器
service.interceptors.request.use(config => {const token = localStorage.getItem('token'); // 假设 token 存储在 localStorageif (token) {config.headers['Authorization'] = `Bearer ${token}`; // 设置 Authorization 头}const requestKey = `${config.method}_${config.url}`;// 获取当前时间戳const currentTime = Date.now();// 检查是否存在已有请求且在1秒内if (pendingRequests.has(requestKey)) {const lastRequestTime = requestTimestamp.get(requestKey);// 只有在1秒内才取消请求if (currentTime - lastRequestTime < 1000) {const cancelTokenSource = pendingRequests.get(requestKey);cancelTokenSource.cancel('Operation canceled due to new request.');}}// 更新时间戳和创建新的 CancelTokenrequestTimestamp.set(requestKey, currentTime);const cancelTokenSource = axios.CancelToken.source();config.cancelToken = cancelTokenSource.token;pendingRequests.set(requestKey, cancelTokenSource);return config;},error => {return Promise.reject(error);}
);// 响应拦截器
service.interceptors.response.use(response => {const requestKey = `${response.config.method}_${response.config.url}`;pendingRequests.delete(requestKey); // 请求完成后删除requestTimestamp.delete(requestKey); // 请求完成后删除时间戳// 根据具体需求处理响应数据return response.data; // 返回数据},error => {const requestKey = `${error.config.method}_${error.config.url}`;pendingRequests.delete(requestKey); // 请求失败后删除requestTimestamp.delete(requestKey); // 请求失败后删除时间戳// 这里可以根据错误状态码做相应处理if (axios.isCancel(error)) {console.log('请求被取消:', error.message);} else {console.error('请求错误:', error);}return Promise.reject(error);}
);export default service;
相关文章:
JavaScript 系列之:Ajax、Promise、Axios
前言 同步:会阻塞。同步代码按照编写的顺序逐行依次执行,只有当前的任务完成后,才会执行下一个任务。 异步:异步代码不会阻塞后续代码的执行。当遇到异步操作时,JavaScript 会将该操作放入任务队列中,继续…...
為什麼使用不限量動態住宅IP採集數據?
在瞭解“不限量動態住宅IP數據採集”之前,我們需要先搞清楚什麼是“動態住宅IP”。簡單來說,動態IP是一種會定期變化的IP地址,通常由互聯網服務提供商(ISP)分配給家庭用戶。與固定IP(靜態IP)不同…...
vue3-06vue2(Object.defineProperty)与vue3(基于ES6的Proxy)的响应式原理对比
1.vue2响应原理 1.1对于对象与数组 对象类型: 通过 object.defineProperty() 对属性的读取、修改进行拦截 (数据劫持) 数组类型:通过重写更新数组的一系列方法来实现拦截。 (对数组的变更方法进行了包裹) Vue2的响应式是基于Object.defineProperty实现的 1.2 基本原理Objec…...
MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part 4
第04章_逻辑架构 1. 逻辑架构剖析 首先MySQL是典型的C/S架构,即Client/Server架构,服务器端程序使用的mysqld。 不论客户端进程和服务器进程是采用哪种方式进行通信,最后实现的效果都是:客户端进程向服务器进程发送一段文本&…...
清华大学DeepSeek文档下载,清华大学deepseek下载(完成版下载)
文章目录 前言一、清华大学DeepSeek使用手册下载二、清华大学DeepSeek使用手册思维导图 前言 这是一篇关于清华大学deepseek使用手册pdf的介绍性文章,主要介绍了DeepSeek的定义、功能、使用方法以及如何通过提示语设计优化AI性能。以下是对这些核心内容的简要概述&…...
HDFS数据多目录、异构存储、回收站
1.NameNode元数据多目录 HDFS集群中可以在hdfs-site.xml中配置“dfs.namenode.name.dir”属性来指定NameNode存储数据的目录,默认NameNode数据存储在${hadoop.tmp.dir}/dfs/name目录,“hadoop.tmp.dir”配置项在core-site.xml中。 我们也可以将NameNod…...
windows设置暂停更新时长
windows设置暂停更新时长 win11与win10修改注册表操作一致 ,系统界面不同 1.打开注册表 2.在以下路径 \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键新建 DWORD 32位值,名称为FlightSettingsMaxPauseDays 根据需求填写数…...
04 路由表的IP分组传输过程
目录 1、路由表的核心结构 2、IP分组传输过程和数据包转发过程 2.1、IP分组传输过程 2.2、数据包转发过程 2.3、IP分组传输过程和数据包转发的区别 3、数据包的变化 3.1、拓扑结构 3.2、传输过程详解(主机A → 主机B) 3.2.1、主机A发送数据 3.2…...
实现Python+Django+Transformers库中的BertTokenizer和BertModel来进行BERT预训练,并将其应用于商品推荐功能
一、环境安装准备 #git拉取 bert-base-chinese 文件#创建 虚拟运行环境python -m venv myicrplatenv#刷新source myicrplatenv/bin/activate#python Django 集成nacospip install nacos-sdk-python#安装 Djangopip3 install Django5.1#安装 pymysql settings.py 里面需要 # 强制…...
数据结构---定长顺序表
1.线性表的定义 存在唯一的一个被称为“第一个”的数据元素;存在唯一的一个被称为“最后一个”的数据元素;除第一个之外,集合中的每一个数据元素都只有一个前驱;除最后一个之外,集合中的每一个数据元素都只有一个后继…...
Elasticsearch 相关面试题
1. Elasticsearch基础 Elasticsearch是什么? Elasticsearch是一个分布式搜索引擎,基于Lucene实现。 Mapping是什么?ES中有哪些数据类型? Mapping:定义字段的类型和属性。 数据类型:text、keyword、integer、…...
冒泡排序(Bubble Sort)详细教程:Java实现与优化
一、什么是冒泡排序? 冒泡排序(Bubble Sort)是一种简单的排序算法,它的基本思想是通过两两比较相邻元素,将较大的元素“冒泡”到数列的末尾。每一轮遍历会将一个较大的元素放到正确的位置,直到整个数组有序…...
【git】【reset全解】Git 回到上次提交并处理提交内容的不同方式
Git 回到上次提交并处理提交内容的不同方式 在 Git 中,若要回到上次提交并对提交内容进行不同处理,可使用 git reset 命令搭配不同选项来实现。以下为你详细介绍操作步骤及各选项的作用。 1. 查看提交历史 在操作之前,可通过以下命令查看提…...
矩阵的 正定(Positive Definite)与负定(Negative Definite):从Fisher信息矩阵看“曲率”的秘密
矩阵的正定与负定:从Fisher信息矩阵看“曲率”的秘密 在数学和统计学中,矩阵的“正定性”和“负定性”是一对重要概念,尤其在优化、统计推断和机器学习中频繁出现。比如,Fisher信息矩阵(Fisher Information Matrix, F…...
Uniapp 小程序:语音播放与暂停功能的实现及优化方案
界面部分 //开启语音 <button class"open" v-if"showPlayfalse" click"playText">这是开启播放的图片</button >//关闭语音 <button class"close" v-if"showPlaytrue" click"stopText">这是…...
Python基于机器学习的微博舆情情感分析系统,微博评论情感分析可视化系统(全新升级)
大家好,今天为大家带来的是Python基于机器学习的微博舆情情感分析系统,微博评论情感分析可视化系统,这个系统在原本的系统上进行优化升级。 算法从开源框架的 snlow ,到支持机器学习的 lstm 算法可以手动输入语句,进行…...
IP-------GRE和MGRE
4.GRE和MGRE 1.应用场景 现实场景 居家工作,公司工作,分公司工作----------需要传输交换数据--------NAT---在该场景中需要两次NAT(不安全) 为了安全有两种手段-----1.物理专线---成本高 2.VPN--虚拟专用网---隧道技术--封装技…...
内网综合渗透测试——WinterMute: 1靶场
靶场来源 <WinterMute: 1 ~ VulnHub> Wintermute 虚拟机网络配置指南 本实验涉及网络跳转技术,需正确配置VirtualBox网络。所有IP均为动态分配,配置快速简便。 通过"文件 >> 导入虚拟设备"导入各虚拟机。 STRAYLIGHT (网络#1 和 …...
项目进度管理工具:甘特图与关键路径法(2025实战指南)
在全球数字化转型加速的背景下,项目延期率高达42%的现状倒逼管理者掌握科学的进度管理工具。本文结合2025年最新实践,深度解析甘特图与关键路径法的原理及应用,助你构建精准可控的项目进度管理体系。 一、双剑合璧:工具组合的价值…...
deepseek-r1-centos-本地服务器配置方法
参考: 纯小白 Centos 部署DeepSeek指南_centos部署deepseek-CSDN博客 https://blog.csdn.net/xingxin550/article/details/145574080 手把手教大家如何在Centos7系统中安装Deepseek,一文搞定_centos部署deepseek-CSDN博客 https://blog.csdn.net/soso67…...
C# Unity 唐老狮 No.2 模拟面试题
本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: Unity课程 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 如果你发现了文章内特殊的字体…...
一周学会Flask3 Python Web开发-flask3上下文全局变量session,g和current_app
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili flask3提供了session,g和current_app上下文全局变量来方便我们操作访问数据。 以下是一个表格,用于比较Flask中的…...
SpringBoot整合Mybatis-Plus+Druid实现多数据源
概述 Spring Boot: Spring Boot是一个基于Spring框架的开源Java开发框架,旨在简化Spring应用程序的开发、配置和部署。它提供了一种快速、敏捷的方式来构建独立的、生产级别的Spring应用程序,同时还提供了许多开箱即用的功能和工具࿰…...
【Mysql】我在广州学Mysql 系列—— 性能优化相关例题
ℹ️大家好,我是练小杰,时间过得真快,还有2天,2025年2月份就结束了!!😆 本文是针对Mysql数据库中有关性能优化的相关示例,通过本文的学习可以深入了解性能优化的各类命令!…...
罗成华教授论腹膜后肿瘤核磁共振检查意义
腹膜后器官很少受生理运动的影响,而MRI又可进行除横断面以外的冠状面、矢状面或其它任意切面检查,其图像清晰,故其特别适用于腹膜后肿瘤的术前检查。早期经验显示MRI可提供比CT更多的信息,不用造影剂术前即…...
CSS3 圆角:实现与优化指南
CSS3 圆角:实现与优化指南 随着网页设计的发展,CSS3 圆角已经成为了现代网页设计中不可或缺的元素之一。本文将详细讲解 CSS3 圆角的基本用法、实现方式以及优化技巧,帮助您在网页设计中更好地运用这一功能。 一、CSS3 圆角基本用法 1.1 基…...
Windows下不建议使用C/C++运行库的本地化功能
Windows不建议setlocale或使用C的std::locale对象等C/C运行库的本地化功能,因为setlocale或C的std::locale对象实现bug多,不稳定,可能存在兼容性问题,如: 1、DOS/Win16下setlocale只支持"C"的locale 2、Wi…...
python-leetcode-乘积最大子数组
152. 乘积最大子数组 - 力扣(LeetCode) class Solution:def maxProduct(self, nums: List[int]) -> int:if not nums:return 0max_prod nums[0]min_prod nums[0]result nums[0]for i in range(1, len(nums)):if nums[i] < 0:max_prod, min_prod…...
基于YOLO11深度学习的半导体芯片缺陷检测系统【python源码+Pyqt5界面+数据集+训练代码】
《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...
Python入门 — 类
面向对象编程中,编写表示现实世界中的事物和情景的类(class),并基于这些类来创建对象(object)。根据类来创建对象称为实例化,这样就可以使用类的实例(instance) 一、创建…...
本地大模型编程实战(22)用langchain实现基于SQL数据构建问答系统(1)
使 LLM(大语言模型) 系统能够查询结构化数据与非结构化文本数据在性质上可能不同。后者通常生成可在向量数据库中搜索的文本,而结构化数据的方法通常是让 LLM 编写和执行 DSL(例如 SQL)中的查询。 我们将演练在使用基于 langchain 链 &#x…...
监听其他音频播放时暂停正在播放的音频
要实现当有其他音频播放时暂停当前音频,你可以使用全局事件总线或 Vuex 来管理音频播放状态。这里我将展示如何使用一个简单的事件总线来实现这个功能。 首先,你需要创建一个事件总线。你可以在项目的一个公共文件中创建它,例如 eventBus.js…...
Docker数据卷操作实战
什么是数据卷 数据卷 是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性: 数据卷 可以在容器之间共享和享用对 数据卷 的修改立马生效对 数据卷 的更新,不会影响镜像数据卷 默认会一直存在,即时容器被…...
Go中slice和map引用传递误区
背景 关于slice和map是指传递还是引用传递,很多文章都分析得模棱两可,其实在Go中只有值传递,但是很多情况下是因为分不清slice和map的底层实现,所以导致很多人在这一块产生疑惑,下面通过代码案例分析slice和map到底是…...
代码审计入门学习
简介 HadSky轻论坛程序为个人原创PHP系统,作者为蒲乐天,后端基于puyuetianPHP框架驱动,前端基于 puyuetianUI框架驱动,默认编辑器为puyuetianEditor富文本编辑器,其他非原创框架及驱动JQuery.js 及Font-Awesome字体库…...
排序算法(3):
这是我们的最后一篇排序算法了,也是我们的初阶数据结构的最后一篇了。 我们来看,我们之前已经讲完了插入排序,选择排序,交换排序,我们还剩下最后一个归并排序,我们今天就讲解归并排序,另外我们还…...
AI革命下的多元生态:DeepSeek、ChatGPT、XAI、文心一言与通义千问的行业渗透与场景重构
前言 人工智能技术的爆发式发展催生了多样化的AI模型生态,从通用对话到垂直领域应用,从数据挖掘到创意生成,各模型凭借其独特的技术优势与场景适配性,正在重塑全球产业格局。本文将以DeepSeek、ChatGPT、XAI(可解释人…...
服务端配置TCP探活,超出探活时间后的行为?
server端启动 (完整源码在最后) 配置探活 setsockopt(client_fd, IPPROTO_TCP, TCP_KEEPIDLE, &(int){5}, sizeof(int)); // 空闲60秒后探测setsockopt(client_fd, IPPROTO_TCP, TCP_KEEPINTVL, &(int){10}, sizeof(int)); // 探测间隔10秒…...
Eclipse安装和配置环境教程包含下载、安装、汉化(附安装包)
文章目录 前言一、JDK 安装二、Eclipse IDE 安装三、Eclipse软件汉化(可选)四、安装完成 前言 在编程的世界里,一款好的开发工具能让效率大幅提升,Eclipse 2024 便是这样的利器。不过,其安装过程涉及 JDK 配置、软件本…...
nginx简单命令启动,关闭等
启动命令 #启动nginx start nginx重启命令 比如修改了配置文件,用这个命令重启生效 #重启nginx nginx -s reload3,查看端口占用 #查看端口占用 netstat -aon4,关闭nginx 如果使用cmd命令窗口启动nginx, 关闭cmd窗口是不能…...
SQL------搭建sql靶场和打开sql靶场及报错解决
搭建sql靶场 1.下载安装包与文件 在官网上下载phpstudy网址: http://www.xp.cn 下载sqli-labs的网址: https://github.com/Audi-1/sqli-labs 2.下载小皮面板 打开安装包 安装,记得改自己想要安装的路径 打开php版本 记得下载5.几的版本&…...
对话式AI引擎:DeepSeek技术引领多模态交互新篇章
摘要 DeepSeek技术公司推出了一项创新服务——“对话式AI引擎”,仅需两行代码即可激活任意大型AI模型的语音对话功能。这项技术使得文本型AI模型迅速转变为具备实时语音对话能力的多模态交互模型,解决了大型AI模型在语音交互方面的不足,为AI行…...
在什么情况下需要使用光谱相机呢?
1.需要捕捉不可见光信息时 光谱相机不仅能捕捉可见光,还能记录红外、紫外等波段的光谱信息。以下场景尤其适用: 环境监测:检测水质、空气污染物等肉眼无法观察的物质。 农业监测:分析植物的近红外反射率,判断作物健…...
nnUNetv2用自己的数据集训练推理
有什么不懂的大家可以在评论区问我,我一定会积极回复哒!!! 一、环境配置 首先创建一个虚拟环境 conda create -n nnunet python3.9 conda activate nnunet 然后在pytorch官网,安装pytorch,这里我安装的是…...
std::thread的同步机制
在 C 中,std::thread 用于创建和管理线程。为了确保多个线程能正确、安全地访问共享资源,避免数据竞争和不一致问题,需要使用同步机制。 互斥锁(std::mutex) 原理:互斥锁是一种最基本的同步原语ÿ…...
Matplotlib 绘图标记
Matplotlib 绘图标记 引言 Matplotlib 是一个功能强大的 Python 绘图库,广泛用于数据可视化。在 Matplotlib 中,绘图标记(markers)是数据点在图表中显示的方式。正确的使用绘图标记可以增强图表的可读性和美观性。本文将详细介绍…...
Web3.py 入门笔记
Web3.py 学习笔记 📚 1. Web3.py 简介 🌟 Web3.py 是一个 Python 库,用于与以太坊区块链进行交互。它就像是连接 Python 程序和以太坊网络的桥梁。 官方文档 1.1 主要功能 查询区块链数据(余额、交易等)发送交易与…...
《论企业集成平台的理解与应用》审题技巧 - 系统架构设计师
企业集成平台的理解与应用——论文写作框架 一、考点概述 本论题“企业集成平台的理解与应用”主要考察的是计算机软件测试工程师对于企业集成平台(EIP)的深入理解以及在实际项目中的应用能力。论题涵盖了以下几个核心内容: 首先ÿ…...
IO 和NIO有什么区别?
IO 与 NIO 的区别详解 Java 中的 IO(Input/Output) 和 NIO(New IO 或 Non-blocking IO) 是两种不同的输入输出处理机制,主要区别体现在设计模型、性能优化和应用场景上。以下是详细对比: 1. 阻塞与非阻塞模…...
音频进阶学习十六——LTI系统的差分方程与频域分析一(频率响应)
文章目录 前言一、差分方程的有理式1.差分方程的有理分式2.因果系统和ROC3.稳定性与ROC 二、频率响应1.定义2.幅频响应3.相频响应4.群延迟 总结 前言 本篇文章会先复习Z变换的有理分式,这是之前文章中提过的内容,这里会将差分方程和有理分式进行结合来看…...