Promise
Promise是ES6中提出的处理异步的异步编程方法
promise能够解决回调顺序的不确定性和回调地狱(回调地狱是指回调代码嵌套程度加深)
1. Promise的状态
promise是一个容器,容器内部保存了异步事件的状态/结果
- pending 等待态
- resolved 执行态
- rejected 拒绝态
- 当状态发生改变时,状态保持不变
2. Promise的状态转移方法
创建一个promise对象:默认参数是两个方法,resolve和reject
- 当异步事件成功则调用resolve方法来传参(传递终值),并且Promise的状态由pending变为resolved
- 若失败则调用reject方法传参(传递拒绝原因),并且Promise的状态由pending变为rejected
const p = new Promise((resolve, reject)=>{
setTimeout(function () {
resolve('hello promise');
}, 2000)
});
3. Promise的then方法
一个Promise必须提供一个then方法以访问/获取当前终值和拒因,Promise的then方法接收两个参数,这两个参数实际上是监听函数
promise.then(onFulfilled, onRejected);
//上面的例子中
p.then(res=>{console.log(res)});
- onFulfilled:监听函数,当Promise中调用resolve便执行onFulfilled
- onRejected:监听函数,当Promsie中调用reject便执行onRejected
then方法必须返回一个Promise对象,根据这个属性我们可以进行链式调用
promise2 = promise1.then(onFulfuilled, onRejected);
// 虽然then方法返回一个Promise对象,但是返回的promise是不同的
// q = p.then((res)=>{console.log(res)}) q:Promise {<resolved>: undefined}
// q = p.then((res)=>{console.log(res); return 1;}) q:Promise {<resolved>: 1}
当需要发起异步请求,不用一直等待异步事件,只需要等有结果了通过then来监听并调用回调函数,并且当存在多个需要顺序执行的异步操作时,能够进行链式调用
4. catch方法
catch方法属于Promise中then方法的语法糖,等价于then(null, (err)=>{})
实际上也是一个then,catch方法与then方法第二个参数其实是一个东西
then方法中的第二个参数只能捕获前一个Promise对象返回的rejected状态
catch方法可以捕获前面某一个Promise对象返回的rejected状态
Promise.resolve("new").then((value)=>{
Promise.resolve("other").then(()=>{
throw new Error("error");
})
}).catch((reason)=>{
console.log(reason);
})
内层的promise确实是抛出了错误,但它没有return给外层promise,此时相当于return undefined,这样就不是错误,catch也就没法捕获
正确做法:
Promise.resolve("new").then((value)=>{
// return promise
return Promise.resolve("other").then(()=>{
throw new Error("error");
})
}).catch((reason)=>{
console.log(reason);
})
5. 封装promise
function returnPromise(){
return new Promise(resolve, reject){
}
}
returnPromise().then(res=>{})
6. Promise的基本用法
- .then(res=>{}, err=>{}) 用于执行回调函数
- .catch() 抓取reject(错误/异常) 等价于 .then(null, (err)=>{})
- .all() 处理多个promise对象
Promise.all([promise1, promise2]).then().catch() 接受一个数组,数组中可以有多个promise对象
当数组中所有promise对象执行都成功则all方法也成功,若有一个不成功,则all方法不成功 - .race() 也是处理多个promise对象
Promise.race([promise1, promise2]).then().catch() 接受一个数组,数组中可以有多个promise对象
race的状态由第一个返回的promise对象的状态所决定,若第一个promise返回resolve则表示成功,若第一个返回reject则表示失败 - .done()/.finally() 不管最后结果是失败还是成功都会执行
- .resolve()/.reject() 将任何数据类型转换成promise对象 eg. let p = Promise.resolve(‘foo’)
7. Promise并发
1. 并发请求
const urls = [
'bytedance.com',
'tencent.com',
'alibaba.com',
'microsoft.com',
'apple.com',
'hulu.com',
'amazon.com'
];
function loadUrl(url) {
return new Promise((res, rej)=>{
const img = new Image();
img.src = url;
img.onload = function() {
console.log('图片资源加载完成');
res();
};
img.onerror = rej;
})
}
Promise.all(urls.map(loadUrl(url))).then(res=>{}); //使用map方法遍历
Promise并发执行多个请求,并且所有请求全部加载完成后再处理数据
2. 限制数量的并发
假设这么一个业务场景:有多个资源的url并存储在一个urls数组中,要求并发下载并且任意时刻同时下载的链接数量不可以超过三个
const urls = [
'bytedance.com',
'tencent.com',
'alibaba.com',
'microsoft.com',
'apple.com',
'hulu.com',
'amazon.com'
];
urls中存储了加载资源的URL
function loadUrl(url) {
return new Promise((res, rej)=>{
const img = new Image();
img.src = url;
img.onload = function() {
console.log('图片资源加载完成');
res();
};
img.onerror = rej;
})
}
函数 loadUrl 加载图片资源,输入一个url并且返回一个Promise,该Promise在图片下载完成时resolve,下载失败reject
let count = 0; //记录并发执行的数量
// 并发加载函数
function currentLoad() {
if (urls.length > 0 && count < 3){ // 判断是否超过一定限制
count++;
loadUrl(urls.shift()).then(()=>{
count--;
}).finally(currentLoad) // .finally结束时进行下一次的加载
}
}
function run() {
// 开始时执行三次
for (let i = 0; i < 3; i++){
currentLoad();
}
}
run();
8. Promise和Async的联系和区别
Promise
和async
都是异步编程的方法,是非阻塞的
联系:async/await实质上是构建在Promise之上的,我们使用async定义的函数最终将会返回一个Promise对象,其then方法中接收的值为async函数中return的内容
async function asyncReq(){
await console.log("async");
return "done";
}
// Promise {<resolved>: "done"}
asyncReq().then((res)=>{console.log(res)}); //done
区别:
- async/await具有更好的语义性,提高代码可读性
async代表函数内部有异步操作
await代表需要等待后面的异步操作 - async/await写起来更像是同步,直接按照想要的顺序进行代码书写;而Promise只能在then方法中进行回调函数的执行
- 对于错误处理,async/await可以同时捕获异步和同步代码抛出的异常,而在promise中,then函数里抛出的异常try/catch是捕获不到的,只能用.catch方法捕获
const makeRequest = () => {
try {
getJSON()
.then(result => {
// then方法中可能会出错,但是try/catch模块中的catch不能够捕获错误
const data = JSON.parse(result)
console.log(data)
})
// 必须放在这个catch方法中
.catch((err) => {
console.log(err)
})
} catch (err) {
console.log(err)
}
}
const makeRequest = async () => {
try {
// 同步代码和异步代码都可以被catch捕获
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}