Promise
简介
Promise是异步编程的一种解决方案(相当于异步操作的同步写法)。
一项操作需要依赖另外一项操作的返回值,如果使用嵌套回调的方式会造成回调地狱的问题,Promise解决了这个问题。
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,
Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise有三个状态:待定,解决,拒绝;Promise状态一旦改变为拒绝或是拒绝就无法再更改。
Promise弊端:
- 一旦执行,不可取消;
- 无法得知执行进度,即:处于待定状态时,不知道是刚刚开始还是快要结束;
使用
创建
创建一个Promise对象,即期约。
通过new Promise()创建
Promise回调函数接收到两个参数:
- resolve :异步操作执行成功后的回调函数
- reject:异步操作执行失败后的回调函数 使用resolve()指定状态为-解决
let p1 = new Promise((resolve,reject)=>{resolve('解决!!')})
console.log(p1)//Promise { '解决!!' }
使用reject()指定状态为-拒绝
let p1 = new Promise((resolve,reject)=>{reject('拒绝!!')})
console.log(p1)//Promise { <rejected> '拒绝!!' } 【并抛出错误!】
执行器函数是同步执行的。
使用resolve()和reject()将状态改变后不可撤销,继续改变状态默认失败(没有效果!)
let p1 = new Promise((resolve,reject)=>{
resolve('解决!!')
reject('失败!!')//不会造成任何效果
})
console.log(p1)//Promise { '解决!!' }
使用定时器保证期约不会一直卡在待定状态,定时退出
let p1 = new Promise((resolve,reject)=>{
//因为状态不可更改,所以如果在定时器内部执行前得到期约解决,定时器内部的reject()就无效了
setTimeout(() => {//2秒后拒绝
reject('拒绝!!')
}, 2000);
})
console.log(p1)//Promise { <pending> } 同步查看-状态为待定
setTimeout(() => {//3秒后其状态为拒绝
console.log(p1)//Promise { <rejected> '拒绝!!' } 【并抛出错误!】
}, 3000);
Promise.resolve()-实例化一个解决期约
let p1 = Promise.resolve('解决!')
console.log(p1)//Promise { '解决!' }
Promise.reject()-实例化一个拒绝期约
let p1 = Promise.reject('拒绝!')
console.log(p1)//Promise { <rejected> '拒绝!' } 【并抛出错误!】
拒绝期约的错误不能被try/catch捕获,只能通过拒绝处理程序捕获。
原因:拒绝期约的错误没有抛到执行同步代码的线程里,而是通过浏览器异步消息队列来处理!
期约的实例方法
期约的实例方法是连接外部同步代码和内部异步代码直接的桥梁。
Promise.prototype.then()
then()接收两个参数
- 第一个参数:onResolved()---期约解决时执行的回调函数
- 第二个参数:onRejected()---期约拒绝时执行的回调函数
//解决的回调能接收到resolve接收的数据
new Promise((resolve,reject)=>{
resolve('解决!!')
}).then(
(data)=>{console.log(data)},//解决
(err)=>{console.log(err)}
)
//拒绝的回调能接收到reject接收的数据
new Promise((resolve,reject)=>{
reject('拒绝!!')
}).then(
(data)=>{console.log(data)},
(err)=>{console.log(err)}//拒绝
)
只需要提供一个回调函数的情况
//只需要解决的回调,那就省略2参
new Promise((resolve,reject)=>{
resolve('解决!!')
}).then(
(data)=>{console.log(data)},//解决
)
//只需要拒绝的回调,那就在1参的位置放上undefined
new Promise((resolve,reject)=>{
reject('拒绝!!')
}).then(
undefined,
(err)=>{console.log(err)}//拒绝
)
注意!
-
如果then()在使用时不传入处理程序,则原样向后传。
-
如果没有显式的返回值,则Promise.resolve()会包装这个值。
-
抛出异常会返回拒绝的期约。
-
返回的错误值会被Promise.resolve()包装。
let p = new Promise((resolve,reject)=>{
resolve('解决!!')
})
//不传入处理程序
let p1 = p.then()
setTimeout(() => {
console.log(p1)//Promise { '解决!!' }
}, 0);
//没有显式的返回值
let p2 = p.then(()=>{})
setTimeout(() => {
console.log(p2)//Promise { undefined }
}, 0);
//抛出异常
let p3 = p.then(()=>{throw '异常!!'})
setTimeout(() => {
console.log(p3)//Promise { <rejected> '错误!!' }
}, 0);
//返回的错误值
let p4 = p.then(()=>{return Error('错误!!')})
setTimeout(() => {
console.log(p4)//Promise {Error: 错误!!at D:......\text.js:22:29}
}, 0);
Promise.prototype.catch()
catch(回调函数) ---在接收到拒绝期约时执行回调函数---可用作最后的捕错手段。
new Promise((resolve,reject)=>{
resolve('解决!!')
}).catch((err)=>{console.log(err)})//解决---无反馈
new Promise((resolve,reject)=>{
reject('拒绝!!')
}).catch((err)=>{console.log(err)})//拒绝---执行回调函数
Promise.prototype.finally()
finally(回调函数) ---无论如何都会执行,其内部回调函数没有办法知道期约状态---可用作添加处理程序。
new Promise((resolve,reject)=>{
resolve('解决!!')
}).finally(()=>{console.log('执行!!')})//解决-回调执行
new Promise((resolve,reject)=>{
reject('拒绝!!')
}).finally(()=>{console.log('执行!!')})//拒绝-回调执行
当期约进入落定状态时,与之相关的处理程序会被排期(进入任务队列),而不是立即执行。
多个处理程序的执行顺序:按照添加它们的顺序执行。
期约连锁
因为每个期约实例的方法都会返回一个新的期约对象,所有可以连缀调用。
let timer = new Date()
let p = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(1)
console.log(1,(new Date() - timer)/1000,'秒后执行')
}, 1000);
})
p.then(()=>{
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(2)
console.log(2,(new Date() - timer)/1000,'秒后执行')
}, 1000);
})
}).then(()=>{
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(3)
console.log(3,(new Date() - timer)/1000,'秒后执行')
}, 1000);
})
}).then(()=>{
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(4)
console.log(4,(new Date() - timer)/1000,'秒后执行')
}, 1000);
})
})
//1 1.002 秒后执行
//2 2.019 秒后执行
//3 3.025 秒后执行
//4 4.035 秒后执行
期约合成
将多个期约组合成为一个期约。
Promise.all()
Promise.all()静态方法创建的期约会在一组期约全部解决之后再解决,其返回值为一个期约。
当期约全部解决---则合成期约的解决值为所有包含期约解决值的数组。
let p1 = Promise.all([
Promise.resolve('解决-1'),
Promise.resolve('解决-2'),
Promise.resolve('解决-3'),
])
setTimeout(() => {
console.log(p1)//Promise { [ '解决-1', '解决-2', '解决-3' ] }
}, 0);
一个待定---则合成期约也待定。
一个拒绝---则合成期约返回第一个拒绝的期约。
let p2 = Promise.all([
Promise.resolve('解决-1'),
Promise.resolve('解决-2'),
Promise.reject('拒绝-1'),//第一个拒绝被返回
Promise.reject('拒绝-2')//后面的拒绝会静默处理
])
setTimeout(() => {
console.log(p2)//Promise { <rejected> '拒绝-1' }
}, 0);
Promise.race()
Promise.race()静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。
let p1 = Promise.race([
Promise.resolve('解决-1'),//第一个落定---被返回
Promise.resolve('解决-2'),//后面的期约---运行不会受影响
Promise.resolve('解决-3'),
])
setTimeout(() => {
console.log(p1)//Promise { '解决-1' }
}, 0);
let p2 = Promise.race([
Promise.reject('拒绝-1'),//第一个落定---被返回
Promise.resolve('解决-1'),
Promise.resolve('解决-2'),
Promise.reject('拒绝-2')//后面的拒绝---静默处理
])
setTimeout(() => {
console.log(p2)//Promise { <rejected> '拒绝-1' }
}, 0);
手写实现
简易实现
//有什么作用? -- 解决回调地狱问题
// 1.结构初始化---使用类
// 2.this指向---注意!
// 3.then实现---在类上添加一个then方法
// 4.执行异常---使用tryCatch捕获并返回一个拒绝的Promise对象
// 5.异步---使用setTimeout模拟
// 6.回调保存---使用数组将每次then中的回调保存,放在最后执行
// 7.链式调用---在then中返回一个新的Promise对象
class MyPromise{
static P = '待定'
static F = '解决'
static R = '拒绝'
constructor(func){
this.status = MyPromise.P //初始化状态为待定
this.value = null//保存解决和拒绝的值
this.resArr = []//保存每次then内部解决的回调函数-回调保存
this.rejArr = []//保存每次then内部拒绝的回调函数-回调保存
try {
func(this.res.bind(this),this.rej.bind(this))//使用bind改变this指向是有必要的
} catch (error) {
this.rej(error)//如果MyPromise内部有异常则返回拒绝的MyPromise对象
}
}
res(value){
setTimeout(()=>{//将res内部代码放在最后执行-回调保存
if(this.status === MyPromise.P){
this.status = MyPromise.F
this.value = value
this.resArr.forEach(fn=>{//-回调保存
fn(value)
})
}
})
}
rej(value){
setTimeout(()=>{//将rej内部代码放在最后执行-回调保存
if(this.status === MyPromise.P){
this.status = MyPromise.R
this.value = value
this.rejArr.forEach(fn=>{//-回调保存
fn(value)
})
}
})
}
then(onRes,onRej){
return new MyPromise(()=>{//返回一个新的MyPromise对象---实现链式调用
onRes = typeof onRes === 'function' ?onRes:()=>{}//如果不是函数则将其置为一个空函数
onRej = typeof onRej === 'function' ?onRej:()=>{}//如果不是函数则将其置为一个空函数
if(this.status === MyPromise.P){//如果状态为待定,则将回调保存,因为状态会异步更改-回调保存
this.resArr.push(onRes)
this.rejArr.push(onRej)
}
if(this.status === MyPromise.F){
setTimeout(()=>{//then为异步,使用定时器将其与同步区分开
onRes(this.value)
})
}
if(this.status === MyPromise.R){
setTimeout(()=>{
onRej(this.value)
})
}
})
}
}
//测试
console.log('同步-1')
let p = new MyPromise((res,rej)=>{
console.log('初始化')
setTimeout(() => {
res('解决!')
rej('拒绝!')//无效
}, 1000);
})
console.log('同步-2')
p.then(
(res)=>{console.log(res)},
(err)=>{console.log(err)}
).then(
(res)=>{console.log(res)},
(err)=>{console.log(err)}
)
console.log('同步-3')
// 同步-1
// 初始化
// 同步-2
// 同步-3
// 解决!
扩展版
添加了catch和resolve、reject、race、all。
class MyPromise{
static P = '待定'
static F = '解决'
static R = '拒绝'
constructor(func){
this.status = MyPromise.P //初始化状态为待定
this.value = null//保存解决和拒绝的值
this.resArr = []//保存每次then内部解决的回调函数-回调保存
this.rejArr = []//保存每次then内部拒绝的回调函数-回调保存
try {
func(this.res.bind(this),this.rej.bind(this))//使用bind改变this指向是有必要的
} catch (error) {
this.rej(error)//如果MyPromise内部有异常则返回拒绝的MyPromise对象
}
}
res(value){
setTimeout(()=>{//将res内部代码放在最后执行-回调保存
if(this.status === MyPromise.P){
this.status = MyPromise.F
this.value = value
this.resArr.forEach(fn=>{//-回调保存
fn(value)
})
}
})
}
rej(value){
setTimeout(()=>{//将rej内部代码放在最后执行-回调保存
if(this.status === MyPromise.P){
this.status = MyPromise.R
this.value = value
this.rejArr.forEach(fn=>{//-回调保存
fn(value)
})
}
})
}
then(onRes,onRej){
return new MyPromise((res,rej)=>{//返回一个新的MyPromise对象---实现链式调用
onRes = typeof onRes === 'function' ?onRes:value=>value//如果不是函数则将其置为一个空函数
onRej = typeof onRej === 'function' ?onRej:err=>{throw err}//如果不是函数则将其置为一个空函数
if(this.status === MyPromise.P){//如果状态为待定,则将回调保存,因为状态会异步更改-回调保存
this.resArr.push(onRes)
this.rejArr.push(onRej)
}
if(this.status === MyPromise.F){
setTimeout(()=>{//then为异步,使用定时器将其与同步区分开
try {
onRes(this.value)
} catch (error) {
rej(error)//如果有异常则返回拒绝的MyPromise对象
}
})
}
if(this.status === MyPromise.R){
setTimeout(()=>{
try {
onRej(this.value)
} catch (error) {
rej(error)//如果有异常则返回拒绝的MyPromise对象
}
})
}
})
}
catch(fn){
return this.then(null,fn)
}
}
//resolve方法
MyPromise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
MyPromise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
//测试
console.log('同步-1')
let p = new MyPromise((res,rej)=>{
console.log('初始化')
setTimeout(() => {
res('解决!')
rej('拒绝!')//无效
}, 1000);
})
console.log('同步-2')
p.then(
(res)=>{console.log(res)},
(err)=>{console.log(err)}
).then(
(res)=>{console.log(res);},
(err)=>{console.log(err)}
).catch(
(err)=>{console.log(err)}
)
console.log('同步-3')
// 同步-1
// 初始化
// 同步-2
// 同步-3
// 解决!
MyPromise.resolve('Promise.resolve---解决!').then((res)=>{console.log(res)},(err)=>{console.log(err)})
MyPromise.reject('Promise.reject---拒绝!').then((res)=>{console.log(res)},(err)=>{console.log(err)})
// Promise.resolve---解决!
// Promise.reject---拒绝!
异步函数
让以同步代码方式写的代码能够异步执行。即用同步方式,执行异步操作。
async
作用:声明异步函数。
async function fn1(){}//函数声明
let fn2 = async function(){}//函数表达式
let fn3 = async ()=>{}//箭头函数
class Test{
async fn4(){}//方法
}
使用async关键字可以使函数具备异步特性,但其代码仍然是同步求值的。参数和闭包方面与同步函数一致。
异步函数return了个啥?
期约:即通过Promise.resolve()包装的Promise对象。
async function fn1(){}//无返回
console.log(fn1())//Promise { undefined }
async function fn2(){return '字符串!'}//有返回
console.log(fn2())//Promise { '字符串!' }
async function fn3(){throw '异常!'}//异步函数内部抛出错误,函数返回拒绝的期约
fn3().catch(console.log)//异常!
async function fn4(){Promise.reject('拒绝!')}//拒绝期约的错误不会被异步函数捕获
fn4().catch(console.log)//无返回--因为无法捕获
//返回期约-返回什么就是什么
async function fn5(){return Promise.resolve('解决!')}
async function fn6(){return Promise.reject('拒绝!')}
fn5().then(
(data)=>{console.log(data)},//解决!
(err)=>{console.log(err)}
)
fn6().then(
(data)=>{console.log(data)},
(err)=>{console.log(err)}//拒绝!
)
await
暂停异步函数代码执行,等待期约解决。
await必须在异步函数内部使用。
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果,也就是说它会阻塞后面的代码,等待 Promise 对象结果。如果等待的不是 Promise 对象,则返回该值本身。
错误处理
如果
await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。
async function test() {
await Promise.reject('错误了')
};
test().then(res=>{
console.log('success',res);
},err=>{
console.log('err ',err);
})
// err 错误了
防止出错的方法,也是将其放在
try...catch代码块之中。
async function test() {
try {
await new Promise(function (resolve, reject) {
throw new Error('错误了');
});
} catch(e) {
console.log('err', e)
}
return await('成功了');
}
多个
await命令后面的异步操作,如果不存在继发关系(即互不依赖),最好让它们同时触发。
let foo = await getFoo();
let bar = await getBar();
// 上面这样写法 getFoo完成以后,才会执行getBar
// 同时触发写法 ↓
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二 利用平行执行
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
其他
-
异步函数性能强于Promise。
-
可以使用生成器手写实现async/await