Promise
Promise
Promise 对象用于表示一个异步操作的最终完成或失败及其结果值,是一种比传统的回调函数和事件调用更加合理的异步结果处理方案。
Promise 并不能创建异步任务,它只是对传统回调函数的改进和封装,常用于封装异步请求,以解决回调地狱等问题;Promise利用微任务使被封装的回调可被加入到当前事件循环中执行。
Promise 可解决的问题:
- 解决回调地狱的问题
- 支持多个请求并发
Promise 的缺点:
- 无法取消 Promise,一旦新建就会立即执行,无法中途取消。
- Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending状态时,无法得知目前进展到哪一个阶段
回调地狱
异步操作时,在请求成功的回调函数里继续写函数,或继续进行异步操作,层层嵌套,就会形成回调地狱。
Promise 的特点
Promise 有三种状态:pending(未完成)、fulfilled(已成功)、rejected(已失败)
Promise 对象的状态改变,只有两种可能:
- 从 pending(未完成)变为 fulfilled(已成功)
- 从 pending(未完成)变为 rejected(已失败)
一旦状态改变,就不会再变,任何时候都可以得到这个结果
基本使用
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。
Promise 对象可以用 then 方法分别指定 resolved 和 rejected 状态的回调函数。
let promise = new Promise(function (resolve, reject) {
if (/* 异步操作成功 */) {
// resolve函数将Promise的状态从“未完成”变为“成功”
resolve(value);
} else {
// reject函数将Promise状态从“未完成”变为“失败”
reject(error);
}
});
// then 方法有两个参数,第一个是成功 resolve 的回调,第二个是失败 reject 的回调
promise.then(
// Promise对象状态为“成功”时执行
function (value) {
},
// Promise对象状态为“失败”时执行
function (error) {
}
)
也可以使用如下写法:catch 方法相当于 then 方法的第二个参数,即失败 reject 的回调。
promise.then( // Promise对象状态为“成功”时执行
function (value) { }
).catch( // Promise对象状态为“失败”时执行
function (error) { }
)
执行过程:
- Promise 新建后会立即同步执行其中的代码
- 调用 resolve 和 reject 并不会像return一样终结 Promise 参数函数的执行,其后的代码还是会被执行
- 当前脚本所有同步任务执行完后才会执行 then 回调;即,不会在本轮事件循环中执行then回调
let promise = new Promise(function (resolve, reject) {
console.log(2)
resolve(3)
console.log(4)
})
// then的第二个函数是可选的,非必须。
promise.then(function (value) {
console.log(value)
})
console.log(5)
// 2
// 4
// 5
// 3
then() 的链式调用
在 then
中通过 return
返回普通数据时,返回的数据会成为下一个 then
的实参;如果通过 return
返回 Promise
对象时,下一个 then
就会是该对象的 then
, 并在该对象执行完成后调用;
例1:then 方法定义在原型对象 Promise.prototype 上,then返回的是一个新的 Promise 实例,因此可以继续调用 then 方法,实现链式调用。多个then链式调用时,前一个then返回结果会成为下一个then的实参;
new Promise((resolve, reject)=> {
resolve(1)
})
.then(val => { // 状态为成功时执行
console.log(val)
return 2
})
.then(value => { // 状态为成功时执行
console.log(value)
})
// 1
// 2
例2:当前then 返回 Promise 对象时,后面的 then 会等待这个 Promise 对象的状态发生变化,才会被调用
function getNum (num) {
let promise = new Promise((resolve, reject)=> {
setTimeout(() => {
resolve(num)
}, 1000)
})
return promise
}
getNum(1)
.then(val => {
console.log(val)
return getNum(2) // 返回Promise对象
})
.then(val => {
console.log(val)
})
// 1
// 2
第一个 then 执行完后返回了一个 Promise 对象,一秒后,返回的 Promise 对象状态从 “未完成” 变为 “成功” ,此时开始执行第二个 then 方法里的回调函数。并且第二个then参数是这个新 Promise 对象中 resolve(num) 中的实参
catch() 方法
跟 then 一样,catch 也定义在原型对象 Promise.prototype 上
let promise = new Promise((resolve, reject)=> {
reject('出错啦')
})
promise.then(val => console.log(val))
.catch(err => console.log(err))
// 等同于
promise.then(val => console.log(val))
.then(null, err => console.log(err))
// 出错啦
// 出错啦
特性说明演示:
let promise = new Promise((resolve, reject)=> { // 解析一
// 这里的代码是在主队列中同步执行,所谓的异步是指Promise的原型方法then、catch、finally,它们会被放在任务队列中等待下一次事件循环时执行
resolve('Promise实例正确')
throw new Error('Promise实例出错'); // 此处抛出异常无效,因为Promise状态已变成resolved, Promise状态不会二次修改,所以抛出的错误不会被捕捉
})
promise.then(val => { // 解析二
console.log(val)
throw new Error('then方法内出错') // 此处抛出异常不受Promise状态影响,会被下一个catch捕获
return '链式调用then'
})
.then(val => { // 解析三
console.log(val)
})
.catch(err => { // 解析四
console.log(err)
throw new Error('catch方法内出错') // 此处抛出异常不受Promise状态影响,会被下一个catch捕获
})
.catch(err => { // 解析五
console.log(err)
// 因为之后没有catch了,所以此处的异常会被浏览器打印至控制台上;但并不影响程序正常执行,因为Promise对象内抛出的错误不会抛到外部,所以不会导致进程退出或脚本终止执行
throw new Error('最后的catch方法出错')
})
setTimeout(() => { // 解析六
console.log('promise后调用') // 因为此处的异步程序是最后一个被放置到任务队列中,因此最后执行
}, 0)
// Promise实例正确
// Error: then方法内出错
// Error: catch方法内出错
// Uncaught (in promise) Error: 最后的catch方法出错
// promise后调用
finally() 方法
finally 方法不管 Promise 对象状态是什么,都会执行。
finally 不接受任何参数,且总是返回原来的值。
new Promise((resolve, reject) => {
resolve(1)
})
.finally(() => {
console.log(2)
})
.then(val => console.log(val))
// 2
// 1
async/await
async/await是用于把复杂难懂的异步代码变成类似同步代码的语法糖;通常情况下,其作用就是用于简化Promise的使用:
let proObj = ()=>{
new Promise(function(resolve, reject) {
if (/* 异步操作成功 */) {
resolve(value); //将Promise的状态由padding改为fulfilled
} else {
reject(error); //将Promise的状态由padding改为rejected
}
})
}
const handleGuess = async () => {
try {
const result = await proObj(); // 代替then方法,await会使用promise直接获得then结果
} catch (error) {
alert(error); // catch方法可以由try、catch来替代
}
};
handleGuess()
通过在函数前使用async关键字,可以创建了一个异步函数,在async函数内的使用方法较之前有如下不同:
- 将await关键字放在Promise前,就可以直接获得then结果。
- 使用try, catch语法来代替promise中的catch方法
async/await在fetch请求中应用:
const fetchCountry = async (alpha3Code) => {
try {
const res = await fetch('https://restcountries.eu/rest/v2/alpha/' + alpha3Code);
const data = await res.json();
return data.borders;
} catch (error) {
console.log(error);
}
};
fetchCountry('cn')
all() 方法
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 all 方法并行执行所有异步操作,并在所有异步操作执行完成后才执行自己的回调。
例1:基本使用
如下:all 方法接受一个 Promise 对象数组,数组中所有Promise对象的结果均为 fulfilled
时,fruits
的状态才会变成 fulfilled
,数组中任何一个Promise
对象被rejected
,fruits
的状态就变成 rejected
,同时第一个被 reject
的实例的返回值,会返回给 fruits
的回调catch
。如果数组中某个Promise对象定义了自己的catch
回调,则一旦被rejected
,并不会触发 Promise.all()
的 catch
方法,而是会触发它自己的catch
,它自己的catch方法返回的是一个新的 Promise
实例,所以实际上这个Promise对象指向的是新的Promise实例,该实例执行完 catch
方法后,也会变成 resolved
,最终导致数组中这个Promise对象的状态变成 resolved
。
let promise1 = new Promise((resolve, reject) => {
resolve(1)
})
let promise2 = new Promise((resolve, reject) => {
throw new Error('出错了')
}).catch(err => err)
let promise3 = new Promise((resolve, reject) => {
reject('又出错了')
})
let fruits = Promise.all([promise1, promise2, promise3])
fruits.then(val => {
console.log(val)
}).catch(err => {
console.log('捕捉到某个Promise对象出错:”' + err + '”')
})
// 捕捉到的出错:”Error: 出错了”
例2:使用async/wait
Array的 map()
方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
const array1 = [1, 4, 9, 16];
// pass a function to map
const map1 = array1.map(x => x * 2);
console.log(map1);
// expected output: Array [2, 8, 18, 32]
当处理一个数组Promise时,需要使用Promise.all:
const fetchCountryAndNeigbors = async () => {
// a. 获取一个then结果
const then = await fetchCountry("cn");
// b. 同时获取多个Promise的then结果
// Promise.all同时接收并返回Promise数组所有Promise的then结果
const thenResultArray = await Promise.all(
['zh-cn','zh-hk'].map((ele) => fetchCountry(ele)); // 此行返回Promise数组,作为Promise.all的参数
);
console.log(neighbors);
};
fetchCountryAndNeigbors();
race() 方法
与 all() 相同,race() 也是用于并发处理多个异步任务,区别在于 all () 是在所有任务都完成后才能得到结果。而 race() 是只要有一个任务执行完成,就能得到结果。
注:Promi.race() 是返回最先完成的请求的结果,而非最先请求成功的响应结果,即race()返回时不管请求是否成功,只要请求有返回 ;
Promise回调特性:
注意事项
1.0> 如下,调用链中如果没有定义catch
,但却执行了reject
,则会报错 Uncaught(in promise)
,即在Promise中未捕获异常;
但没有定义 then
的情况下,执行 resolve
却不会报出异常;
new Promise(function(resolve, reject) {
if (/* 异步操作成功 */) {
resolve(value); //将Promise的状态由padding改为fulfilled
} else {
reject(error); //将Promise的状态由padding改为rejected
}
})
2.0> 创建Promise对象快捷方式
创建Promise对象,并立即执行resolve
Promise.resolve(value)
等同于
new Promise(function(resolve){
resolve(value);
});
Promise.reject也是同理;
3.0> 除了执行 reject 会执行 catch 外,其它任何异常的抛出都会导致 catch 被执行;
4.0> Promise内部用try catch 来捕获的异常,而try catch不能捕获异步异常;
如下,发生异步异常时只会在控制台报错,但不会执行reject
或catch
函数;
new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error('nono') // 主动抛出异步错误
}, 500);
}).then(()=>{},(err)=>{
console.log(1) // 这里是reject时应该调用的函数,但是这里并没有执行,只会在控制台报错
}).catch((err)=>{
console.log(2) // 这里catch都不执行
console.log(err)
})
异步异常只能通过如下方式捕获
setTimeout(() => {
try {
throw new Error('出错了')
} catch (e) {
console.log(e)//代码执行的时候,这里就能捕获到上面抛出的异常错误了
}
}, 400)
5.0> axios完全支持Promise,其拦截器、then、catch与Promise调用完全相同;即,拦截器中的return,会直接触发后续的then;
详见:
https://chenminzhe.com/2020/04/05/ES6%E4%B8%ADPromise%E8%AF%A6%E8%A7%A3/
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises