一. Promise的含义
- Promise是异步编程的一种解决方案,比传统的解决方案:回调函数和事件更加合理和强大
- Promise其实就是一个容器,里面保存着未来才会结束的某个事件(通常是异步操作)
- Promise对象有以下两个特点:
- 对象的状态不受外界影响。Promise对象代表的是异步操作,有三种状态,pending进行中,fulfilled已成功,rejected已失败
只有异步操作的结果才能改变状态,这也是Promise承诺,名字的由来
- 一旦状态改变就不会再改变,任何时候都可以得到这个结果。
- Promise对象的状态改变情况:(1) pending改变为fulfilled;pending改变为rejected;
- 只要改变为fulfilled或者rejected,那么状态就凝固了,不会再改变,此时被称为
resolved已定型 但是一般resolved都是特指fulfilled状态,而不包括rejected状态
- Promise的一些缺点:
(1) 无法取消Promise,
一旦新建就会立即执行,无法中途取消 (2) 如果不设置回调函数,那么Promise内部抛出的错误不会反映到外部(3) 当处于pending状态时,无法得知目前发展到哪个阶段刚刚开始还是即将完成
二. 基本用法
1. 基本含义
- ES6规定,Promise对象是一个构造函数,可以用来生成Promise实例,
new Promise()的参数函数中有两个参数,调用第一个参数函数会改变状态为resolved状态,调用第二个会改变状态为rejected状态 - Promise实例生成之后,可以使用then方法指定resolved,rejected状态的回调函数,
第一个参数函数在resolved状态时调用,第二个参数在rejected状态时调用 - 下面是一个Promise实例:
var p=new Promise(function(res,rej){
console.log("刚刚new Promise()就会执行,即使是赋值操作")
// res();// 状态改为resolved
rej();// 状态改为rejected
})
// 获取状态
p.then(()=>{
console.log('状态改变为resolved')
},()=>{
console.log('状态改变为rejected')
})
2. setTimeout和promise实例
function pro(time){
return new Promise((res,rej)=>{
// new Promise之后就执行,res会在time时间后执行,从而状态改变为resolved
setTimeout(res,time)
})
}
console.log(Math.floor(new Date().getTime()/1000))
pro(1000).then(()=>{
console.log(Math.floor(new Date().getTime()/1000))
console.log("在1000ms后执行")
})
3. Promise异步结合同步实例
let p=new Promise(function(res,rej){
console.log("new Promise代码执行,会立即执行该步骤")
console.log(1);
res()
})
p.then(()=>{
console.log("then操作时异步操作,会在本轮宏任务结束之前才执行")
console.log(3)
})
console.log(2);
4. ajax请求实例
var get=function(url){
return new Promise((res,rej)=>{
var xhr=new XMLHttpRequest();
xhr.open("GET",url);
xhr.onreadystatechange=function(){
if(this.readyState==4&&this.status==200){
res(this.response)
}
}
xhr.onerror=(err)=>{
rej(err)
}
xhr.send();
})
}
// http://127.0.0.1:8849,此时会报错(因为本地没开启该端口)
get("http://127.0.0.1:8848/MyHxsj/面经.md").then((data)=>{
console.log(data)
},(err)=>{ console.log(err) })
5. resolve返回一个Promise实例
- 如果调用resolve函数或者reject函数返回时带有参数。
- 那么如果
resolve函数的参数是一个Promise实例时,返回的实例的状态决定了当前Promise实例的状态 如果返回的Promise实例的状态是pending,那么当前Promise实例会等待pending状态改变如果返回的Promise实例的状态是resolved或者rejected,那么回调函数就会立即执行
// 1. 返回的Promise实例的状态为pending
var p1=new Promise(function(res,rej){
console.log("pending")
setTimeout(function(){
res("resolved")
},2000)
})
var p2=new Promise(function(res,rej){
res(p1);// resolved状态,但是传递的参数为Promise实例
})
p2.then((data)=>{
console.log(data)
console.log("在返回的Promise实例p1状态为resolved之后执行")
})
/*
pending
// 两秒之后
resolved
在返回的Promise实例p1状态为resolved之后执行
*/
// 2. 返回的Promise实例的状态为resolved
var p3=new Promise(function(res){
res("p3")
})
var p4=new Promise(function(res,rej){
res(p3)
})
p4.then((res)=>{
console.log(res);//p3,立即执行
})
6. resolve之后的同步代码执行
promise实例改变状态为resolve不会改变同步代码的执行
// 1. resolved
new Promise((res,rej)=>{
res(1);
console.log("此前状态为resolved,但是依旧会执行本行代码")
}).then((data)=>{
console.log(data)
})
console.log('宏任务代码')
/*
此前状态为resolved,但是依旧会执行本行代码
宏任务代码
1
*/
// 2. rejected
new Promise((res,rej)=>{
rej(new Error("错误error:"));
console.log("此时状态为rejected,本行代码会执行")
}).then(()=>{},(err)=>{
console.log(err)
})
/*
此时状态为rejected,本行代码会执行
// 然后才会轮到微任务执行
Error: 错误error:
at 平时测试.html:130
at new Promise (<anonymous>)
at 平时测试.html:129
*/
7.改变状态后即刻结束
// 如果想要在改变状态为resolved或者rejected之后
// 不会继续执行往下代码,那么使用return
new Promise(function(res,rej){
return res(1)
// return rej(1)
console.log("已经return,所以不会继续执行本行代码")
}).then((data)=>{
console.log(data)
},(err)=>{
console.log(err)
})
// 结果只会打印: 1
三. then()
- then方法是定义在原型对象Promise.prototype上的,作用是为Promise实例添加状态改变时的回调函数
then方法的参数不是函数时会发生穿透。也就是then(1).then((data)=>{}),第二个then获得的参数函数的data就是上次穿透的数据1- 如果then方法的参数是函数,那么第一个参数是resolved函数,第二个参数是rejected函数
如果promise实例存在多个then,那么下一个then会等待上一个then返回的promise实例状态改为resolved/rejected才会执行
// 多个then,需要等待上一个then的状态改为resolved/rejected
new Promise((res,rej)=>{
res(1)
}).then((res)=>{
console.log(res);
return new Promise((res,rej)=>{
setTimeout(res,1000);// 一秒后,状态改为resolve
})
}).then(()=>{
console.log(2)
})
/*
1
// 1秒后
2
*/
- 如果then返回的不是Promise实例对象,那么会发生值穿透
new Promise((res,rej)=>{
res(1)
}).then(2).then((data)=>{
console.log(data);//1
return 3;
}).then((data)=>{
console.log(data)//3
})
/*
res(1),函数res传递的是一个number类型的变量
then(2),不是函数,没有传递值,所以无效,仅仅是一个语句 2
// 所以1的值会继续穿透,then(2)无效
1,第二个then属于函数对象,所以能够接收到数据1
return 3,传递数据3
最后一个then,打印3,接收到上个then函数返回的数据3
*/
四. catch()
1. 基本含义
Promise.prototype.catch()方法相等于Promise.prototype.then(null,reject())或者Promise.prototype.then(undefined,reject())- 用于指定发生错误时的回调函数。在异步操作抛出错误时,状态变为rejected,就会调用catch方法
- 另外在then方法指定的回调函数中抛出错误,就会被catch方法捕获
调用完catch方法,状态会变为resolved!
// 1. 捕获Promise错误
new Promise(function(res,rej){
throw new Error("抛出错误")
}).catch(function(err){
console.log("err:",err)
})
/*
err: Error: 抛出错误
at 平时测试.html:116
at new Promise (<anonymous>)
at 平时测试.html:115
*/
// 2. then方法中抛出错误
new Promise(function(res,rej){
res(1)
}).then(function(data){
throw new Error("then err")
}).catch(function(err){
console.log(err)
})
/*
Error: then err
at 平时测试.html:131
*/
2. catch()相当于调用reject
在promise的后面使用catch其实相当于在promise内部加上try-catch语句,或者相当于加上一个reject状态改变(在某个条件符合时)
// 1. catch方法相等于try-catch语句
new Promise(function(res,rej){
try{
throw new Error("try抛出错误")
}catch(e){
console.log(e)
}
})
/*
Error: try抛出错误
at 平时测试.html:117
at new Promise (<anonymous>)
at 平时测试.html:115
*/
// 2. catch方法还相等于调用reject方法然后在then的第二个参数监听
new Promise(function(res,rej){
rej("rej err")
}).then(()=>{},(err)=>{
console.log(err)
});//rej err
3.resolve状态再抛出
由于catch相当于调用rejected状态,并且状态改为rejected后不可以改为resolve状态所以Promise状态为resolve时,再抛出错误,无法在catch捕获到
// 1. res(),resolve状态后不可以改为reject状态
new Promise(function(res,rej){
res("ok");//状态为resolve
throw new Error("此时抛出错误无法被捕获到")
}).catch(function(err){
console.log(err)
}).then((data)=>{
console.log(data);//ok
})
4. catch与then第二个参数
- then处于catch之前,则在catch之前就捕获了错误
new Promise(function(res,rej){
throw new Error("err")
}).then(()=>{},(err)=>{
console.log("reject:",err)
}).catch(function(data){
console.log('catch:',data)
})
/*
reject: Error: err
at 平时测试.html:116
at new Promise (<anonymous>)
at 平时测试.html:115
*/
5. Promise内部抛出的错误不会影响到外部
// 1. new Promise内部会抛出错误
new Promise(function(res,rej){
throw new Error("err")
})
// 2.即使上面抛出了错误,剩余的代码依旧会执行
// 也就是Promise内部抛出的错误不会影响到外部
setTimeout(()=>{
console.log("继续执行")
},1000)
// 会打印错误,之后打印 "继续执行"
6. try-catch捕获错误
- try-catch捕获错误分为:
try执行之前,try执行过程中,try执行之后
// 1.语法错误,在执行try代码之前就抛出错误,所以catch无法捕获到
/* try{
a.
}catch(e){
console.log("myerr:",e)
} */
/*
Uncaught SyntaxError: Unexpected token '}'
*/
// 2. 在try代码执行过程中抛出的错误(可以捕获到)
/* function add(x){return x+y}; // 错误函数
try{
console.log(add(1))
}catch(e){
console.log(e);//ReferenceError: y is not defined
} */
// 3. 在try代码执行完毕之后抛出的错误(不能捕获)
/* try{
setTimeout(function(){
console.log('定时器')
// 此时的错误没有被catch捕获到
a.b;//Uncaught ReferenceError: a is not defined
},2000)
}catch(e){
console.log("捕获到的错误:",e);
} */
// 4.try/catch无法捕获到Promise异常时因为Promise内部就捕获到异常了,没有往外抛出异常,所以外部的catch无法捕获到异常
try{
new Promise(function(res,rej){
// 当没有在Promise中设置catch时,提示下面语句
throw new Error("err");//Uncaught SyntaxError: Missing catch or finally after try
}).then(()=>{},(err)=>{
console.log("第二个参数:",err)
/*
第二个参数: Error: err
at 平时测试.html:147
at new Promise (<anonymous>)
at 平时测试.html:145
*/
})
.catch(function(err){
console.log("catch:",err)
/* 没有设置then第二个参数函数时
catch: Error: err
at 平时测试.html:147
at new Promise (<anonymous>)
at 平时测试.html:145
*/
})
}catch(e){
console.log("promise:",e);// 无法捕获到promise内部错误
}
7. catch返回的是Promise实例
- 由于catch返回的是Promise实例,
所以catch后面接上的then可以继续执行 catch中也可以继续抛出错误,然后被后面的then或者catch继续捕获
// 1. catch捕获抛出的错误后,then方法可以继续执行
new Promise(function(){
throw new Error("err")
}).catch(function(err){
console.log(err)
}).then(function(){
console.log("catch捕获错误后,还可以继续执行then")
})
/*
Error: err
at 平时测试.html:116
at new Promise (<anonymous>)
at 平时测试.html:115
catch捕获错误后,还可以继续执行then
*/
// 2. catch中也可以抛出错误,然后被下一个catch捕获
new Promise(function(res,rej){
throw new Error("第一个错误")
}).catch(function(err){
console.log(err)
throw new Error("第二个错误")
}).catch(function(err){
console.log(err)
})
/*
Error: 第一个错误
at 平时测试.html:133
at new Promise (<anonymous>)
at 平时测试.html:132
Error: 第二个错误
at 平时测试.html:136
*/
五. finally
ES2018才引入的,就是不管promise的状态是什么,最后都会执行finally方法的回调函数不能接受任何参数,这就意味着不能知道前面的Promise状态到底是fulfilled还是rejected- 所以finally方法里面的操作,应该是和状态无关的,不依赖于Promise的执行结果
- finally方法的本质上就是在promise实例最后加上一个then来接收resolved和rejected两种状态,而加上finally的好处就在于只需要写一个接收函数就行了,不需要知道状态
并且finally方法总是会返回原来的值
// 1. finally在最后
new Promise(function(res,rej){
res(1)
}).then((data)=>{
console.log(data);//1
return 2
}).finally(()=>{
console.log(3);//3, finally方法不能接受到参数,所以2接收1不到
})
/*
1
3
*/
// 2. finally后面还有then方法
new Promise((res,rej)=>{
res(11)
}).then((data)=>{
console.log(data);//11
return 22;
}).finally(()=>{
console.log(33);//33
}).then((data)=>{
console.log(data);//22,执行上一个被返回的then
})
// 3. finally后面还有then,并且finally返回值
new Promise((res,rej)=>{
res(111)
}).then((data)=>{
console.log(data);//111
return "后续then接收到之前的then,而不会接收到finally return的变量"
}).finally(()=>{
console.log(222);//222
return 333;
}).then((data)=>{
console.log(data);//后续then接收到之前的then,而不会接收到finally return的变量
})
六. Promise.all()
- Promise.all()方法用于将多个Promise实例包装成一个新的Promise实例。
Promise.all()接收一个具有Iterator接口的数据作为参数,关键在于成员必须都是Promise实例成员都变为resolved之后会返回结果数组;如果有一个成员变为rejected状态,那么返回rejected状态Promise实例的返回值
// 1. 全部resolved
var p1=new Promise((res,rej)=>{
res(1);
})
var p2=new Promise((res,rej)=>{
res(2);
})
var p3=new Promise((res,rej)=>{
res(3);
})
// Promise.all()返回的是一个Promise实例
// 该实例的值存储在内部属性 [[PromiseValue]]
// 想要获取,需要通过then方法来保存参数
var res1=Promise.all([p1,p2,p3])
.then((data)=>{
console.log(data);//[1, 2, 3]
return data;
})
console.log(res1);//Promise {<pending>}
// 2. 有一个rejected,其他的停止执行
var p4=new Promise((res,rej)=>{
console.log("p4")
setTimeout(res(4),5000);
})
// 2.1 错误没被捕获
var p5=new Promise((res,rej)=>{
setTimeout(rej("error:"+5555),1000)
})
var p6=new Promise((res,rej)=>{
console.log("p6")
res(6)
})
/* var res2=Promise.all([p4,p5,p6]).then((data)=>{
console.log("all:",data);
}) */
// 2.2
/* var p5=new Promise((res,rej)=>{
setTimeout(rej("error:"+5555),1000)
}).catch(function(err){
console.log("err",err);//err error:5555
}).then((res)=>{
return "res"
})
console.log(p5) */
// 2.3 给Promise.all后面添加catch捕获错误
var res2=Promise.all([p4,p5,p6]).then((data)=>{
console.log("all:",data);
}).catch((err)=>{
console.log("all+err",err);//all+err error:5555
})
/*
2.1 没有给p5设置catch捕获错误,所以最后执行错误,Promise.all没接住错误
2.2 给p5设置catch捕获错误,则Promise.all剩余实例也会执行,但是返回rej的实例的值可能是undefined
也就是Promise.all的结果是[4,undefined,6]
2.3 给Promise.all后面添加catch捕获错误,打印all+err error:5555
2.4 如果实例和Promise.all都设置了catch,那么使用实例的catch来捕获错误,因为实例离得近
*/
多个任务异常
- 使用promise.all时存在多个异步任务异常,catch
只捕获最早的那个错误
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
function runReject (x) {
const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res =>console.log(res))
.then(res =>console.log(res),rej=> console.log('error:',rej))
.catch(err =>console.log(err))
多个异步任务是并行执行的,哪个先执行完毕不一定当all方法出现异常时,all之后的then接收的是reject函数,参数就是第一个异常返回的参数当all方法之后的then不存在reject函数时,则catch接受异常参数即使不是all之后的第一个then存在reject函数,只要之后的then存在reject函数,就不会轮到catch去接收异常如果异步任务中存在报错,那么res不会执行,rej才会执行,也就是报错如果不存在报错,那么就会执行res
七. promise.race()
- race方法也是接收一组异步任务,然后并行执行异步任务,但是
只保留第一个执行结果,剩下的异步任务仍在执行,但是执行结果无法获取 - promise.race虽然只会接收第一个任务的结果,但是
其他异步任务依旧会执行
function runAsync(x) {
const p = new Promise(r =>
setTimeout(() => r(x, console.log(x)), 1000)
);
return p;
}
function runReject(x) {
const p = new Promise((res, rej) =>
setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
);
return p;
}
/*
首先打印0,然后返回rej('Error:0')
该rej被Promise.race的catch捕获,但是剩下的成员依旧执行
所以runAsync还会打印 1 2 3
*/
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
.then(res =>console.log("result: ", res))
.catch(err =>console.log(err));// Error:0
八. Promise.resolve()
将现有的对象转换为resolve状态的异步对象就使用Promise.resolve()
// 1. 使用Promise.resolve()
var res1=Promise.resolve("1");
console.log(res1);//Promise {<resolved>: "1"}
// 2. 等同于 new Promise((res,rej)=> res())
var res2=new Promise((res,rej)=>{
res("2")
})
console.log(res2);//Promise {<resolved>: "2"}
四种参数情况
- 参数为Promise实例,
那么就相当于Promise.resolve啥都没做,无效
- 原封不动的返回原来的Promise实例
- 参数是一个thenable对象
- thenable对象指的是具有then方法的对象
- Promise.resolve方法会把thenable对象的then方法执行,然后resolve方法的函数参数就是then中传递的参数
注意thenable对象需要有参数res/rej,然后执行res(xxx)用于传递数据
var a={
then:function(res){
res(444)
}
}
Promise.resolve(a).then((data)=>{
console.log(data);//444
})
- 参数是没有then方法的对象或者不是对象
此时直接传递该参数
// 1. 参数不是对象
Promise.resolve('hello world').then((data)=>{
console.log(data);//hello world
})
// 2. 参数是对象,但是没有then方法
Promise.resolve({a:'hello'}).then((data)=>{
console.log(data);//{a: "hello"}
})
- 不带参数
不带参数就then接收不到数据,还是可以使用!
九. Promise.reject()
- Promise.reject()方法会返回一个新的Promise实例,实例状态为rejected
与Promise.resolve()不同之处在于,reject无论参数为什么,都是直接返回该参数!注意要使用catch接收,否则会报错,因为是reject状态
// 1. thenable对象,直接返回参数
var obj={
then:function(res,rej){
rej("err")
}
}
Promise.reject(obj).catch((e)=>{
console.log(e);//{then: ƒ}
})
//2. 其他参数
Promise.reject("he").catch((res)=>{
console.log(res);// he
})
十. 应用
1. 手写promise
function mypromise(func){
var that=this;
// 回调函数集
that.funclist=[];
// resolve方法
function resolve(value){
// 微任务
setTimeout(()=>{
that.data=value
that.funclist.forEach((callback)=>{
callback(value)
})
})
}
// 执行用户传入的函数
func(resolve.bind(that))
}
mypromise.prototype.then=function(onResolved){
var that=this;
return new mypromise(resolve => {
that.funclist.push(function(){
var res=onResolved(that.data);
if(res instanceof mypromise){
res.then(resolve)
}else{
resolve(res);
}
})
})
}
const func=resolve => {
setTimeout(()=>{
resolve(1)
},1000)
}
var promise1 = new mypromise(func)
promise1.then(res => {
console.log(res)
return new mypromise(resolve => {
setTimeout(() => {
resolve(2)
}, 500)
})
})
console.log(promise1);
2. 预加载图片
// 用于预加载图片
function loadImg(url){
return new Promise(function(res,rej){
var img=document.createElement('img');
img.src=url; // 设置了src,也就是默认开始请求图片资源了
img.onload=res;//指向函数res
img.onerror=rej;
})
}
console.log(loadImg("http://yiyeblog.com/imgs/ES6.png"))
- 可以在NetWork中看到成功加载图片
本文使用 mdnice 排版