开始之前
JavaScript是单线程语言,所以都是同步执行的,要实现异步就得通过回调函数的方式,但是过多的回调会导致回调地狱,代码既不美观,也不易维护,所以就有了promise;Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。Promise 是一个拥有 then
方法的对象或函数。
问题探讨
下面通过多个示例来感受一下不使用 promise
时,处理相应问题的不易,及生成了不便阅读的代码。
定时嵌套
假设我们想要实现网页上一个盒子先向右边移动,移动到一定距离后逐渐缩小,缩小到一定大小后停止缩小。下面我们用两个定时器去实现,一个定时器控制移动,当该定时器结束后启动另一个定时器控制缩小。
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
<style type="text/css">
#box{
height: 100px;
width: 100px;
background-color: #3498db;
position: absolute;
}
</style>
</head>
<body>
<div id="box"></div>
<script type="text/javascript">
function interval(callback,delay=100){
let id = setInterval(()=>callback(id),delay)
}
const box = document.getElementById('box');
interval((timeId)=>{
const left = Number.parseInt(window.getComputedStyle(box).left);
box.style.left = left + 10 + 'px';
if(left > 200){
clearInterval(timeId);
interval((timeId)=>{
const width = Number.parseInt(window.getComputedStyle(box).width);
const height = Number.parseInt(window.getComputedStyle(box).height);
box.style.width = width - 5 + 'px';
box.style.height = height - 5 + 'px';
if(width <= 50){
clearInterval(timeId)
}
})
}
});
</script>
</body>
</html>
图片加载
图片加载完成后设置一个图片边框
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
</head>
<body>
<script type="text/javascript">
function loadImg(file,resolve,reject){
const img = new Image();
img.src = file;
img.onload = () => {
resolve(img);
};
img.onerror = () => {
reject(new Error('load fail'))
}
document.body.appendChild(img);
}
loadImg('https://static.collectui.com/shots/4418237/health-landing-page-large',
image => {
console.log('图片加载成功,开始修改边框');
image.style.border = "5px solid #3498db";
},
error => {
console.log(error);
}
)
</script>
</body>
</html>
疫情解除,员工复工通告
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
</head>
<body>
<script type="text/javascript">
function notice(msg,then){
then(msg);
}
function work(){
notice('全国上下正在积极应对疫情变化', msg=>{
console.log(msg);
notice('疫情已经结束,请各级政府通知企业复工', msg => {
console.log(`收到党中央,国务院消息:${msg}`);
setTimeout(()=>{
notice('请各企业有序复工', msg => {
console.log(`收到政府消息:${msg}`);
notice('请员工在规定时间内到到岗上班,但仍需注意防护', msg => {
console.log(`收到企业消息:${msg}`);
setTimeout(()=>{
notice('员工已到岗',msg => {
console.log(`收到员工消息:${msg},通知结束`);
})
},1000)
})
})
},1000)
})
})
}
work();
</script>
</body>
</html>
上面的实例可以看出在没有使用Promise
的时候,一些相互关联的异步操作就会一层层的嵌套,形成了回调地狱,而且对于阅读和维护代码带来了极大的不便。
Promise 基本语法
Promise 初体验
在开始讲解基本语法之前先改一下疫情复工通知的例子,感受一下promise
带来的便利。
<!DOCTYPE html>
<html>
<head>
<title>JavaScript</title>
</head>
<body>
<script type="text/javascript">
let country = new Promise((resolve,reject)=>{
console.log('全国上下正在积极应对疫情变化');
resolve('疫情已经结束,请各级政府通知企业复工');
})
let goverment = country.then(msg=>{
console.log(`收到党中央,国务院消息:${msg}`);
return {
then(resolve){
setTimeout(()=>{
resolve('请各企业有序复工');
},1000);
}
}
})
let enterment = goverment.then(msg=>{
console.log(`收到政府消息:${msg}`);
return{
then(resolve){
setTimeout(()=>{
resolve('请员工在规定时间内到岗上班,但仍需注意防护')
},1000);
}
}
})
let employee = enterment.then(msg=>{
console.log(`收到企业消息:${msg}`);
return{
then(resolve){
setTimeout(()=>{
resolve('员工已到岗');
},1000)
}
}
})
employee.then(msg=>{
console.log(`收到员工消息:${msg},通知结束`);
})
</script>
</body>
</html>
使用promise后,保证实现了原有功能,写法上各级专注负责自己所管辖的内容,避免了代码的相互嵌套,使代码变得更加容易阅读,维护起来也更加方便。
理解Promise
promise
可以按照字面意思理解——承诺。那这次疫情来讲。国家政府通知全民可以进行正常的工作生活就是对人民的一种承诺。如果疫情控制住了,结束了就是成功,我们就可以不用在家办公或者隔离了。如果疫情没有彻底控制住,就是拒绝,我们仍好继续现在的生活工作状态。
- 一个
promise
必须有一个then
的方法用于处理状态和改变。
状态说明:
Promise包含pending
fulfilled
reject
三种状态 -
pending
指初始等待状态,初始化promise
的状态 -
resolve
指已经解决,将promise
状态设置为fulfilled
-
reject
指拒绝处理,将promise
状态设置为rejected
-
promise
是生产者,通过resolve
与reject
函数告知结果 -
promise
非常适合需要一定执行时间的异步任务(一般网络请求都是异步任务) - 状态量一旦改变就不可改改变
promise是一种队列状态,就像多米多骨牌,状态由上个传递到下一个,然后一个个往下传,当然其中任何一个promise
也是可以改变状态的。
promise在没有使用resolve
和reject
更改状态时,状态为pending
console.log(
new Promise((resolve,reject)=>{});
) // Promise(<pending>)
promise使用ressolve
或reject
改变状态后
console.log(
new Promise((resolve, reject) => {
resolve("fulfilled");
})
); //Promise {<resolved>: "fulfilled"}
console.log(
new Promise((resolve, reject) => {
reject("rejected");
})
); //Promise {<rejected>: "rejected"}
promise
创建任务时会立即执行同步任务,then
会放在异步微任务中执行,需要等待同步任务执行后才执行。
let promise = new Promise((resolve,reject)=>{
resolve('fulfilled');
console.log('第一次打印');
});
promise.then(msg=>{
console.log(msg);
});
console.log('第二次打印')
上面的结果可以看出,
promise
状态改变后产生的微任务会在所有宏任务执行后在执行。
promise
操作都是在其他代码后执行,下面会先输出第一次输出
,之后才会弹出success
-
promise
的then
catch
finally
的方法都是异步任务 - 程序需要将主任务执行完才会执行一步队列任务
let promise = new Promise((resolve,reject)=>{resolve('success')})
promise.then(alert);
alert('第一次输出');
promise.then(()=>{
alert('success之后弹出')
})
下面这个例子将在3秒后将promise
状态设置为fulfilled
,然后执行then
方法
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('fulfilled');
},3000)
}).then(
(msg)=>{
console.log(msg);
},
(error)=>{
console.log(error);
}
)
状态改变了就不能再修改了,下面先通过resolve
改变为成功状态,表示promise
状态已经完成,就不能用使用reject
更改状态了
new Promise((resolve, reject) => {
resolve("操作成功");
reject(new Error("请求失败"));
}).then(
msg => {
console.log(msg);
},
error => {
console.log(error);
}
);
下面的例子中p2返回了p1的状态已经无意义了,后面then时对p1状态的处理。即如果resolve
参数是一个promise
将会改变他的状态。
let p1 = new Promise((resolve,reject)=>{
resolve('fulfilled');
// reject('reject');
});
let p2 = new Promise((resolve,reject)=>{
resolve(p1);
}).then(
value=>{
console.log(value);
},
reason=>{
console.log(reason);
}
);
当promise
作为参数传递时,需要等待promise
执行完才可以继承,下面的p2需要等待p1执行完成。
- 因为
p2
的resolve
返回了p1
的promise,所以此时p2
的then
方法已经时p1
的状态了 - 正因为以上原因
then
的第一个函数输出了p1
的resolve
的参数
let p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('操作成功');
},2000)
})
let p2 = new Promise((resolve,reject)=>{
resolve(p1);
})
p2.then(msg=>{
console.log(msg);
}).catch(error=>{
console.log(error)
})
then的使用
一个promise
需要提供一个then
方法来访问promise
的结果,then
相当于定义当promise
状态发生改变的处理,即promise
处理异步操作,then
用来处理结果。
promise就像疫情中的真政府,then就是民众,如果疫情被控制住了就分fulfilled, 如果没有抑制,就reject。那么民众(then)就要处理不同的状态。
then
方法必须返回promise
,用户返回或者系统自动返回。第一个函数在
resolve
状态时执行,即执行resolve
时执行then
的第一个函数处理成功的状态第二个函数在
reject
状态时执行,即执行reject
时执行then
的第二个函数处理失败状态,该函数是可选的。两个函数都接收
promise
传出的值做为参数也可以使用
catch
来处理失败的状态(上面的最后一个例子)-
如果
then
返回promise
,下一个then
会在当前promise
状态改变后继续执行语法说明
then
的语法如下,onFulfilled函数处理fulfilled
状态。onReject函数处理rejected
状态
- onFulfilled 或 onRejected 不是函数将被忽略
- 两个函数只会被调用一次
- onFulfilled在promise执行成功时调用
- onRejected在promise执行拒绝时调用
promise.then(onFulfilled, onRejected)
基础知识
then
会在promise
执行完成后执行,then
第一个函数在resolve
成功状态执行
let promise = new Promise((resolve, reject) => {
resolve("success");
}).then(
value => {
console.log(`解决:${value}`);
},
reason => {
console.log(`拒绝:${reason}`);
}
);
then
中第二个参数在失败状态执行
let promise = new Promise((resolve, reject) => {
reject("is error");
});
promise.then(
msg => {
console.log(`成功:${msg}`);
},
error => {
console.log(`失败:${error}`);
}
);
如果只关心失败时状态,then
的第一个参数传递null
let promise = new Promise((resolve, reject) => {
reject("is error");
});
promise.then(null, error => {
console.log(`失败:${error}`);
});
promise
传向then
的值,如果then
没有可处理的函数,会一直向后传递(现实中一般不会这么写)
let p1 = new Promise((resolve, reject) => {
reject("rejected");
})
.then()
.then(
null,
f => console.log(f)
);
下面这里例子 如果 onFulfilled
不是函数且promise
执行成功,p2执行成功并返回相同值
let promise = new Promise((resolve, reject) => {
resolve("resolve");
});
let p2 = promise.then();
p2.then().then(resolve => {
console.log(resolve);
});
如果 onRejected 不是函数且promise拒绝执行,p2 拒绝执行并返回相同值
let promise = new Promise((resolve, reject) => {
reject("reject");
});
let p2 = promise.then(() => {});
p2.then(null, null).then(null, reject => {
console.log(reject);
});
链式调用
每次then
都是一个全新的promise
,默认then
返回的状态是fulfilled
let promise = new Promise((resolve,reject)=>{
resolve('fulfilled');
}).then(resolve=>{
console.log(resolve);
}).then(resolve=>{
console.log(resolve);
})
每次的
then
都是一个全新的promise
,不要以为上一个promise
状态会影响以后then
返回的状态
let p1 = new Promise(resolve=>{
resolve();
});
let p2 = p1.then(()=>{
console.log('这是对p1进行处理')
});
p2.then(()=>{
console.log('这是对p2进行处理')
});
console.log(p1); // Promise {<resolve>}
console.log(p2); // Promise {<pending>}
# 再试试把上面两行放在 setTimeout里
setTimeout(() => {
console.log(p1); // Promise {<resolved>}
console.log(p2); // Promise {<resolved>}
});
下面这个例子 then
是对上个promise 的rejected
的处理,每个 then
会是一个新的promise,默认传递 fulfilled
状态
new Promise((resolve, reject) => {
reject();
})
.then(
resolve => console.log("fulfilled"),
reject => console.log("rejected")
)
.then(
resolve => console.log("fulfilled"),
reject => console.log("rejected")
)
.then(
resolve => console.log("fulfilled"),
reject => console.log("rejected")
);
/*执行结果*/
rejected
fulfilled
fulfilled
在看下面一个例子
new Promise((resolve,reject)=>{
reject('error')
}).then(res=>console.log(res),error=>{console.log(error);})
.then(res=>console.log(res),err=>console.log(err))
/*执行结果*/
error
undifine
在看一个例子
new Promise((resolve,reject)=>{
reject('error')
}).then(res=>console.log(res),error=>{console.log(error);return 123})
.then(res=>console.log(res),err=>console.log(err))
/*执行结果*/
error
undifine
在上面我已经介绍过在 promise
实例中,如果状态改变为fulfilled,则可以返回resolve函数,然后做为参数传到then中成功处理函数中,如果状态为rejected,则可以传到reject函数,然后作为参数传递到then
中的拒绝函数中处理。而在then方法中是没有JS部署好的resolve和reject函数的。再往上的第四个例子红可以看到P2的状态是pending的。但是我们可以字then方法中通过设置返回值,创建新的Promise对象之后将返回值传递到下一个then方法中的成功或拒绝函数中去,如果没有返回值则默认为undefined。就是上面两个例子所要表述的内容。
如果内部返回promise
时,将使用该promise
let p1 = new Promise(resolve=>{
resolve();
});
let p2 = p1.then(()=>{
return new Promise((resolve,reject)=>{
resolve('内部中的Promise');
})
});
p2.then(res=>{
console.log(res);
}); // 内部中的Promise
如果 then
返回promise
时,后面的then
就是对返回的 promise
的处理,需要等待该 promise 变更状态后执行。
let promise = new Promise(resolve => resolve());
let p1 = promise.then(() => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`p1`);
resolve();
}, 2000);
});
}).then(() => {
return new Promise((a, b) => {
console.log(`p2`);
});
});
Thenables
包含 then
方法的对象就是一个 promise
,系统将传递 resolvePromise 与 rejectPromise 做为函数参数。
文章开头的地方使用Promise复写的复工通知就是用的该方法。
当然类也可以
new Promise((resolve, reject) => {
resolve(
class {
static then(resolve, reject) {
setTimeout(() => {
resolve("解决状态");
}, 2000);
}
}
);
}).then(
res => {
console.log(`fulfilled: ${res}`);
},
err => {
console.log(`rejected: ${err}`);
}
);
如果对象中的 then 不是函数,则将对象做为值传递
new Promise((resolve, reject) => {
resolve();
})
.then(() => {
return {
then: "后盾人"
};
})
.then(v => {
console.log(v);
});
利用Promise用原声JS封装AJAX
function request(url){
return new Promise((resolve,reject)=>{
let XHR = XMLHttpRequest ? new XMLHttpRequest(): new window.ActiveXObject('Microsoft.XMLHTTP');
XHR.onreadystatechange = funciton(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}
}else{
reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
}
}
XHR.open('GET', url, true);
XHR.send(null);
})
}
catch
除了正常使用reject,Promise里面发生任何的异常,都会触发失败的状态。
下面使用未定义的变量会触发失败状态
let promise = new Promise((resolve, reject) => {
abc;
}).then(
value => console.log(value),
reason => console.log(reason)
);
如果onFulfilled
或onReject
抛出异常,则p2拒绝执行并返回拒绝原因
let promise = new Promise((resolve,reject)=>{
throw new Error('fail');
});
let p2 = promise.then();
p2.then().then(null,reject=>{
console.log(reject);
})
catch用于失败状态的处理函数,等同于 then(null,reject){}
- 建议使用
catch
处理错误 - 将
catch
放在最后面用于统一处理前面发生的错误
const promise = new Promise((resolve, reject) => {
reject(new Error("Notice: Promise Exception"));
}).catch(msg => {
console.error(msg);
});
catch
可以捕获之前所有的promise
的错误,所以将catch
放在最后。下面中例子中catch
捕获了第一个个then
返回的promise
的错误。
new Promise((resolve, reject) => {
resolve();
})
.then(() => {
return new Promise((resolve, reject) => {
reject(".then ");
});
})
.then(() => {})
.catch(msg => {
console.log(msg);
});
错误是冒泡的操作的,下面没有任何一个then
定义第二个函数,将一直冒泡到 catch
处理错误
new Promise((resolve,reject)=>{
reject(new Error('请求失败'));
})
.then(msg=>{})
.then(msg=>{})
.catch(error=>{
console.log(error)
})
catch
也可以捕获对 then
抛出的错误处理
new Promise((resolve, reject) => {
resolve();
})
.then(msg => {
throw new Error("这是then 抛出的错误");
})
.catch((error) => {
console.log(error);
});
建议将错误交给catch
处理,而不是在 then
中完成。
处理机制
在promise
中抛出的错误也会在catch中捕获,其实可以理解为在内部自动执行了try...catch...
new Promise((resolve, reject) => {
try {
throw new Error("fail");
} catch (error) {
reject(error);
}
}).catch(msg => {
console.log(msg);
});
但像下面的在异步中 throw
将不会触发 catch
,而使用系统错误处理
new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error("fail");
}, 2000);
}).catch(msg => {
console.log(msg + "异步处理");
});
已经处理过的将不会在catch重复处理
new Promise((resolve,reject)=>{
reject('error')
}).then(null,error=>{
console.log(error);
}).then()
.catch(error=>{
console.log(error)
})
在 catch
中发生的错误也会抛给最近的错误处理
new Promise((resolve, reject) => {
reject();
})
.catch(msg => {
throw new Error('error')
})
.then(null, error => {
console.log(error);
});
定制错误
可以根据不同的错误类型进行定制操作,下面将参数错误与404错误分别进行了处理
<script type="text/javascript">
class ParamError extends Error{
constructor(message){
super(message);
this.name = 'ParamError'
}
}
class HttpError extends Error{
constructor(message){
super(message);
this.name = 'HttpError'
}
}
function request(url){
return new Promise((resolve,reject)=>{
if(!/^http/.test(url)){
throw new ParamError('请求地址错误');
}
let XHR = new XMLHttpRequest();
XHR.onreadystatechange = funciton(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}else if(XHR.statux === 404){
reject(new HttpError('网址不存在'));
}
}else{
reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
}
}
XHR.open('GET',url,true);
XHR.send(null);
})
}
request('http://www.abc.com')
.then(response=>{
console.log(response)
})
.catch(error=>{
if(error instanceof ParamError){
console.log(error.message)
}
if(error instanceof HttpError){
alert(error.message);
}
console.log(error)
})
</script>
finally
无论状态是resolve
或 reject
都会执行此动作,finally
与状态无关。
const promise = new Promise((resolve, reject) => {
reject("hdcms");
})
.then(msg => {
console.log("resolve");
})
.catch(msg => {
console.log("reject");
})
.finally(() => {
console.log("resolve/reject状态都会执行");
});
下面使用 finally
处理加载状态,当请求完成时移除加载图标。
request('http://localhost:8080')
.then(response=>{
console.log(response)
})
.catch(error=>{
console.log(error)
})
.finally(()=>{
this.loading = false;
})
实例操作
下面是封装的timeout
函数,使用定时器操作更加方便
function timeout(times) {
return new Promise(resolve => {
setTimeout(resolve, times);
});
}
timeout(3000)
.then(() => {
console.log("3秒后执行");
return timeout(1000);
})
.then(() => {
console.log("执行上一步的promise后1秒执行");
});
封闭 setInterval
定时器并实现动画效果
<body>
<style>
div {
width: 100px;
height: 100px;
background: yellowgreen;
position: absolute;
}
</style>
<div></div>
</body>
<script>
function interval(delay = 1000, callback) {
return new Promise(resolve => {
let id = setInterval(() => {
callback(id, resolve);
}, delay);
});
}
interval(100, (id, resolve) => {
const div = document.querySelector("div");
let left = parseInt(window.getComputedStyle(div).left);
div.style.left = left + 10 + "px";
if (left >= 200) {
clearInterval(id);
resolve(div);
}
}).then(div => {
interval(50, (id, resolve) => {
let width = parseInt(window.getComputedStyle(div).width);
div.style.width = width - 10 + "px";
if (width <= 20) {
clearInterval(id);
}
});
});
</script>
接口扩展
resolve
使用 promise.resolve
方法可以快速的返回一个promise对象
Promise.resolve('success').then(value=>{
console.log(value);
})
下面将请求结果缓存,如果再次请求时直接返回带值的 promise
<script type="text/javascript">
const api = 'http://localhost:3000';
function request(url){
return new Promise((resolve,reject)=>{
if(!/^http/.test(url)){
throw new ParamError('请求地址错误');
}
let XHR = new XMLHttpRequest();
XHR.onreadystatechange = function(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}else if(XHR.status === 404){
reject(new HttpError('网址不存在'));
}else{
reject(new Error("Request was unsuccessful: " + XHR.statusText));
}
}
}
XHR.open('GET',url,true);
XHR.send(null);
})
}
</script>
<script type="text/javascript">
function queryAllNumbers(){
const cache = queryAllNumbers.cache || (queryAllNumbers.chche = new Map());
if(cache.has('numbers')){
console.log('走缓存了');
return Promise.resolve(cache.get('numbers'))
}
return request(`${api}/User/GetClassNumber`)
.then(response=>{
cache.set('numbers',response.Data);
console.log('没走缓存');
return response.Data;
})
}
queryAllNumbers()
.then(response=>{
console.log(response)
})
</script>
reject
和 Promise.resolve
类似,reject
生成一个失败的promise
Promise.reject("fail").catch(error => console.log(error));
下面使用 Project.reject
设置状态
new Promise(resolve => {
resolve("ac=bc");
})
.then(v => {
if (v != "123") return Promise.reject(new Error("fail"));
})
.catch(error => {
console.log(error);
});
all
使用Promise.all
方法可以同时执行多个并行异步操作,比如页面加载时同进获取课程列表与推荐课程。
- 任何一个
Promise
执行失败就会调用catch
方法 - 适用于一次发送多个异步操作
- 参数必须是可迭代类型,如Array/Set
- 成功后返回
promise
结果的有序数组
下例中当 p1、p2 两个 Promise 状态都为 fulfilled
时,ps状态才为fulfilled
。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第一个Promise");
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第二个Promise");
}, 1000);
});
let ps = Promise.all([p1, p2])
.then(results => {
console.log(results);
})
.catch(msg => {
console.log(msg);
});
根据用户名获取用户,有任何一人用户获取不到时 promise.all
状态失败,执行 catch
方法
<script type="text/javascript">
function request(url){
return new Promise((resolve,reject)=>{
if(!/^http/.test(url)){
throw new ParamError('请求地址错误');
}
let XHR = new XMLHttpRequest();
XHR.onreadystatechange = function(){
if(XHR.readyState === 4){
if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
try{
let response = JSON.parse(XHR.responseText);
resolve(response);
}catch(e){
reject(e);
}
}else if(XHR.status === 404){
reject(new HttpError('网址不存在'));
}else{
reject(new Error("Request was unsuccessful: " + XHR.statusText));
}
}
}
XHR.open('GET',url,true);
XHR.send(null);
})
}
const api = 'http://localhost:3000';
const promises = ['zhangsan','lisi'].map(name=>{
return request(`${api}/Course/GetCourseInfoById?name=${name}`)
})
Promise.all(promises)
.then(response=>{
console.log(response);
})
.catch(error=>{
console.log('Error:' + error);
})
</script>
可以将其他非
promise
数据添加到 all
中,它将被处理成 Promise.resolve
const promises = [
ajax(`${api}/user?name=zhangsan`),
ajax(`${api}/user?name=lisi`),
{ id: 3, name: "wangwu", email: "wangwu@qq.com" }
];
如果某一个promise
没有catch 处理,将使用promise.all
的catch处理
let p1 = new Promise((resolve, reject) => {
resolve("fulfilled");
});
let p2 = new Promise((resolve, reject) => {
reject("rejected");
});
Promise.all([p1, p2]).catch(reason => {
console.log(reason);
});
allSettled
allSettled
用于处理多个promise
,只关注执行完成,不关注是否全部执行成功,allSettled
状态只会是fulfilled
。
下面的p2 返回状态为 rejected
,但promise.allSettled
不关心,它始终将状态设置为 fulfilled
。
let p1 = new Promise((resolve, reject) => {
resolve("resolved");
});
let p2 = new Promise((resolve, reject) => {
reject("rejected");
});
Promise.allSettled([p1, p2])
.then(msg => {
console.log(msg);
})
下面是获取用户信息,但不关注某个用户是否获取不成功
let promises = [
ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
ajax(`${api}/User/GetCourseInfoById?name=lisi`)
];
Promise.allSettled(promises).then(response => {
console.log(response);
});
race
使用Promise.race()
处理容错异步,和race
单词一样哪个Promise快用哪个,哪个先返回用哪个。
- 以最快返回的promise为准
- 如果最快返加的状态为
rejected
那整个promise
为rejected
执行catch - 如果参数不是promise,内部将自动转为promise
下面将第一次请求的异步时间调整为两秒,这时第二个先返回就用第二人。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第一个Promise");
}, 2000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("第二个Promise");
}, 1000);
});
Promise.race([p1, p2])
.then(results => {
console.log(results);
})
.catch(msg => {
console.log(msg);
});
获取用户信息,如果两秒内没有结果 promise.race
状态失败,执行catch
方法
let promises = [
ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
new Promise((a, b) =>
setTimeout(() => b(new Error("request fail")), 2000)
)
];
Promise.race(promises)
.then(response => {
console.log(response);
})
.catch(error => {
console.log(error);
});
任务队列
如果 then
返回promise
时,后面的then
就是对返回的 promise
的处理
下面使用 forEach
构建的队列,有以下几点需要说明
-
then
内部返回的promise
更改外部的promise
变量 - 为了让任务继承,执行完任务需要将
promise
状态修改为fulfilled
function queue(nums) {
let promise = Promise.resolve();
nums.map(n => {
promise = promise.then(v => {
return new Promise(resolve => {
console.log(n);
resolve();
});
});
});
}
queue([1, 2, 3, 4, 5]);
下面再来通过 reduce
来实现队列
function queue(nums) {
return nums.reduce((promise, n) => {
return promise.then(() => {
return new Promise(resolve => {
console.log(n);
resolve();
});
});
}, Promise.resolve());
}
queue([1, 2, 3, 4, 5]);
async/await 语法糖
使用 async/await
是promise 的语法糖,可以让编写 promise 更清晰易懂,也是推荐编写promise 的方式。
-
async/await
本质还是promise,只是更简洁的语法糖书写 -
async/await
使用更清晰的promise来替换 promise.then/catch 的方式
async
下面在 foo
函数前加上async,函数将返回promise,我们就可以像使用标准Promise一样使用了。
下面在 foo
函数前加上async,函数将返回promise,我们就可以像使用标准Promise一样使用了。
async function foo() {
return "foo foo foo";
}
console.log(hd());
foo().then(value => {
console.log(value);
});
如果有多个await 需要排队执行完成,我们可以很方便的处理多个异步队列
async function foo(message) {
return new Promise(resolve => {
setTimeout(() => {
resolve(message);
}, 2000);
});
}
async function run() {
let h1 = await foo("第一次输出");
console.log(h1);
let h2 = await foo("第二次输出");
console.log(h2);
}
run();
await
使用 await
关键词后会等待promise 完成
-
await
后面一般是promise,如果不是直接返回 -
await
必须放在 async 定义的函数中使用 -
await
用于替代then
使编码更优雅
下例会在 await 这行暂停执行,直到等待 promise 返回结果后才继执行。
async function foo(message){
let p = new Promise(resolve=>{
setTimeout(()=>{
resolve(message)
},2000)
})
let result = await p;
console.log(result);
}
foo('输出输出');
一般await后面是外部其它的promise对象
async function foo(message) {
return new Promise(resolve => {
setTimeout(() => {
resolve(message);
}, 2000);
});
}
async function run() {
let h1 = await foo("第一次输出");
console.log(h1);
let h2 = await foo("第二次输出");
console.log(h2);
}
run();
错误处理
async 内部发生的错误,会将必变promise对象为rejected 状态,所以可以使用catch
来处理
async function foo(){
console.log(bar);
}
foo().catch(error=>{
console.log(error);
})
多个 await 时当前面的出现失败,后面的将不可以执行
async function foo() {
await Promise.reject("fail");
await Promise.resolve("success").then(value => {
console.log(value);
});
}
foo();
如果对前一个错误进行了处理,后面的 await 可以继续执行
async function foo() {
await Promise.reject("fail").catch(e => console.log(e));
await Promise.resolve("success").then(value => {
console.log(value);
});
}
foo();
也可以使用 try...catch
特性忽略不必要的错误
async function foo() {
try {
await Promise.reject("fail");
} catch (error) {}
await Promise.resolve("success").then(value => {
console.log(value);
});
}
foo();
并发执行
有时需要多个await 同时执行,有以下几种方法处理,下面多个await 将产生等待
async function p1() {
return new Promise(resolve => {
setTimeout(() => {
console.log("houdunren");
resolve();
}, 2000);
});
}
async function p2() {
return new Promise(resolve => {
setTimeout(() => {
console.log("hdcms");
resolve();
}, 2000);
});
}
async function foo() {
await p1();
await p2();
}
foo();
使用 Promise.all()
处理多个promise并行执行
async function foo() {
await Promise.all([p1(), p2()]);
}
foo();