前端JSPromi:axios请求结果后⾯的.then()是什么意思?
Promi 是JS中⼀种处理异步操作的机制,在现在的前端代码中使⽤频率很⾼。Promi 这个词可能有点眼⽣,但你肯定见过 (...).then(res => {...});⽤于异步请求的 axios 返回的就是⼀个 Promi 对象。
特种部队训练营平时⼀直在代码中 .then() .catch() 地写来写去,终于决定要认真学⼀学这个 Promi 到底是怎么回事,希望这篇学习笔记也能帮到你。
Promi 对象
⼀个 Promi 对象表⽰⼀个异步操作的执⾏结果,包括状态(成功/失败)和值(成功返回值/错误原因)。⼀个 Promi 在创建的时候异步操作可能还没执⾏完成,通过持有这个 Promi 对象,可以在未来异步操作完成的时候对结果进⾏相应操作。
Promi 对象的状态
这⾥有⼏个常⽤的名词,⽤来表⽰ Promi 对象当前的具体状态:
Pending 待定:刚创建时的初始状态,还没有确定执⾏结果
Fulfilled 已兑现:异步操作执⾏成功,并返回⼀个值
Rejected 已拒绝:异步操作执⾏失败,并返回⼀个错误原因
Settled 已敲定 / Resolved 已决议:“待定”状态的反⾯,都表⽰异步操作已经执⾏完成,即已兑现或已拒绝
回调函数
如果完全不关⼼异步操作的执⾏结果,那就把它放在那⾃⼰执⾏就可以了;但通常情况下我们总是要对操作执⾏的结果进⾏后续处理的,例如更改页⾯上的数据显⽰、错误处理等。但由于异步操作不知道什么时候可以执⾏完成,就出现了“回调函数”的概念,意思就是等到异步操作处理结束了,再回过头来调⽤这个函数来对执⾏结果进⾏处理。
传统做法是,在执⾏异步操作的时候就把回调函数作为参数传进去,⽐如最常见的:
tTimeout(function(){
console.log("成功!");
}, 250);
tTimeout() 函数是最常见的异步函数之⼀,众所周知它的作⽤就是在指定时间后执⾏指定代码。仔细看就会发现,tTimeout() 函数接收两个参数,第⼆个参数是等待时间,⽽第⼀个参数就是回调函数,即等待指定的时间之后要回来调⽤这个函数。
很显然这种传参的做法有很多不⽅便的地⽅,⽐如把对结果的后续处理和异步操作本⾝耦合在了⼀起,以及著名的回调地狱:
doSomething(function(result) {
doSomethingEl(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
Promi.then() 绑定回调函数
有了 Promi 之后,就能把回调和异步操作本⾝分开了。⽆论⼀个 Promi 对象当前是否已经执⾏完毕,我们都能在它上⾯绑定回调函数,并且保证回调函数被执⾏;这就是喜闻乐见的 then() ⽅法。
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
then() ⽅法的语法很简单,有两个可选参数,分别代表当 Promi 的状态变为成功(fulfilled)和失败(rejected)时所使⽤的回调函数。
如果只想绑定 onRejected(即失败时的错误处理函数),下⾯两种写法完全等价,第⼆种是第⼀种的
简写形式。
p.then(null, failureCallback);
p.catch(failureCallback);
使⽤ Promi:链式调⽤
如果只是⽤ then 来绑定回调函数,那并不能解决回调地狱的问题。然⽽很妙的地⽅来了:Promi ⽀持链式调⽤:
doSomething().then(function(result) {
return doSomethingEl(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
链式调⽤的实现
能做到链式调⽤的魔法来⾃ then() ⽅法:它会在执⾏相应的回调函数之后,返回⼀个新的 Promi 对象,并且插⼊ Promi 链的当前位置。
这⾥稍微有点绕,容易把回调函数等同于 then() ⽅法本⾝。实际上成功/失败的回调函数只是 then() 的参数⽽已;⽽实际执⾏ then() 的时候,它会先根据promi 的状态调⽤相应的回调函数,再根据回调函数的执⾏结果⽣成⼀个新的 Promi 对象并返回;具体的对应规则如下:
回调函数执⾏情况then() 返回的 Promi 对象
返回值return x;fulfilled 状态,参数为 x
直接返回return; / ⽆ return 语句fulfilled 状态,参数为 undefined
肥沃的意思
抛出错误throw err;rejected 状态,参数为 err
返回已决议的 Promi状态和参数与返回的 Promi ⼀致
bsp返回未定的 Promi未定的 Promi,回调参数与返回的相同
下⾯这个例⼦中,初始 Promi 的状态为已拒绝,然后第⼀个 then() 调⽤了绑定的 onRejected,返回了状态为 fulfilled 的新 Promi 对象,并传递给了链中的下⼀个 then():
.then(() => 99, () => 42) // 调⽤ onRejected(return 42;),表格中的第⼀种情况
.then(solution => console.log('Resolved with ' + solution)); // Resolved with 42
同时,你可能还记得 then() 的参数定义,两个回调函数都是可选的;如果没有传⼊对应的回调函数,then() 会直接把原 promi 的终态返回,不做额外处理。错误处理
遇到异常抛出(被拒绝的 promi)时,浏览器会顺着 Promi 链寻找下⼀个 onRejected 回调函数
(经常被简写为 .catch()),并跳过中间的 onFulfilled 回调
函数。这种执⾏逻辑与同步代码中的 try-catch 执⾏过程⾮常相似:
// 异步 Promi
doSomething()
.then(result => doSomethingEl(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);
// 同步
out mantry {
let result = syncDoSomething();
let newResult = syncDoSomethingEl(result);
let finalResult = syncDoThirdThing(newResult);
console.log(`Got the final result: ${finalResult}`);
} catch(error) {
failureCallback(error);
}
⼀个具体的例⼦:
.then(() => {
throw new Error('出错');
console.log('a');
})
.then(() => {
console.log('b');
})
.catch(() => {
console.log('c');
})
.then(() => {
console.log('d');
});cristian
// 输出结果:
/
/ "c"
// "d"
常见错误
doSomething().then(function(result) {
doSomethingEl(result) // 没有返回 Promi 以及没有必要的嵌套 Promi
.then(newResult => doThirdThing(newResult));
}).then(() => doFourthThing());
// 最后,是没有使⽤ catch 终⽌ Promi 调⽤链,可能导致没有捕获的异常
上⾯这个例⼦在 Promi 中进⾏了嵌套,但没有将嵌套的 Promi 对象返回,因此doFourthThing() 不会等待 doSomethingEl() 或 doThirdThing() 完成,⽽是并⾏运⾏;并且如果有传⼊参数,接收到的会是 undefined ⽽不是 doThirdThing() 的执⾏结果。
正确的写法应该是:
注:箭头函数 () => x 是 () => { return x; } 的简写,即返回了新的 Promi 对象
doSomething()
.then(function(result) {
integralreturn doSomethingEl(result);
})
.then(newResult => doThirdThing(newResult))
.then(() => doFourthThing())
tmb.catch(error => console.log(error));
创建 Promi 对象
roller如果要执⾏的异步操作没有返回 Promi 对象,可以⽤ new 和构造器创建⾃⼰的 promi。构造器的两个参数的作⽤是在异步操作成功/失败时,转换 Promi 对象的状态并传递对应参数。
const myFirstPromi = new Promi((resolve, reject) => {
// 做⼀些异步操作,最终会调⽤下⾯两者之⼀:
// resolve(someValue); // fulfilled
// reject("failure reason"); // rejected
});
// ⼀个例⼦
高一英语辅导书function myAsyncFunction(url) {
return new Promi((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.nd();
});
};
Promi 其他静态⽅法
创建已决议的 Promi 对象
const p1 = new Promi((resolve, reject) => {
resolve();
});
const p2 = solve();
如果 resolve(value) 的参数是带有 then() ⽅法的 Promi 对象,函数将返回其⾃带 then() ⽅法的执⾏结果;如果参数为空或是基本类型,返回的Promi对象与在 then() ⽅法中 return 对应值的结果⼀致,参见上⽂表格。基于这样的特性, resolve(value) ⽅法可以⽤于将不确定是否为 Promi 对象的 value 值统⼀为Promi。
多个 Promi 对象
Promi.all(iterable)
参数列表中的所有的 promis 都成功时,返回⼀个 fulfilled 的 Promi 对象,参数值是所有 promis 成功返回值的列表(顺序不变)
如果任何⼀个 promi 失败,⽴即返回⼀个 rejected 的 Promi 对象,参数是这个失败 promi 的错误信息
Promi.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => { /* u result1, result2 and result3 */ });
Promi.allSettled(iterable)
列表中所有 promis 都已敲定后返回⼀个promi,并带有⼀个对象数组,对应每个promi 的结果。
Promi.any(iterable)
当列表中的任意⼀个 promi 成功时,⽴即返回这个 promi 的值。
Promi.race(iterable)
当列表中任意⼀个 promi 成功或失败时,⽴即返回该 promi 对象的执⾏结果。
⼀个综合例⼦(使⽤ tTimeout 模拟异步操作):
// 创造⼀个状态为 fulfilled,参数为"foo"的 Promi 对象
.then(function(string) { // string: "foo"
// 返回状态为 fulfilled,参数为"foobar"的对象
return new Promi(function(resolve, reject) {
tTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
.then(function(string) { // string: "foobar"
tTimeout(function() {
string += 'baz';
console.log(string);
}, 1)
/
/ 返回值"foobar"
return string;
})
.then(function(string) { // string: "foobar"
console.log("Last Then");
console.log(string);
});dnr
// 输出结果:
// Last Then
// foobar
// foobarbaz(由第⼆个 then 中的 tTimeout 输出)
结语&参考⽂献
以上是阅读学习了 MDN ⽂档后个⼈总结的学习笔记,可能存在错误和疏漏,欢迎指正与讨论!