一、Promise的理解和使用
Promise是JS中进行异步编程的新解决方案(旧方案是单纯使用回调函数)
从语法上来说,Promise是一个构造函数;从功能上来说,Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值
Promise相对于回调函数的优势:
1. 支持链式调用,可以解决回调地狱问题
2. 指定回调函数的方式更加灵活
Promise可以包裹一个异步操作
// 生成随机数
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1)) + m - 1;
}
// 点击按钮,1s后显示是否中将(30%概率中奖)
const btn = document.querySelector('#btn')
btn.addEventListener('click', function () {
// Promise形式实现
// resolve,reject两个参数都是函数类型的数据
const p = new Promise((resolve, reject) => {
setTimeout(() => {
// 获取1-100的随机数
let n = rand(1, 100);
// 判断
if (n <= 30) {
resolve(n); //将promise对象p的状态设置为成功
} else {
reject(n); //将promise对象p的状态设置为失败
}
}, 1000);
});
// 调用then方法
// 其中第一个回调是对象成功时的回调,第二个回调是对象失败时的回调
p.then((value) => {
alert('恭喜恭喜!中奖啦!,您的中将数字为' + value)
}, (reason) => {
alert('再接再厉,您的号码为' + reason)
})
})
Promise的状态
实例对象中的一个属性 [[PromiseState]]
pending 未决定的
resolved / fulfilled 成功
rejected 失败
Promise 对象的值
实例对象中的另一个属性 [[PromiseResult]]
保存着异步任务(成功/失败)的结果
只有resolve、reject这两个函数可以对这个值进行修改
then用来指定成功和失败的回调,而catch只能指定失败的回调,catch等于是把then的后一个函数拆出来,单独做成语法糖
promise里面的函数是同步,它的then方法是异步
如果传入的参数为 非Promise 类型的对象,则返回的结果为成功的 promise对象
如果传入的参数为 Promise 类型的对象,则参数的结果决定了resolve的结果
成功的结果是每一个promise对象成功的结果组成的数组,失败的结果是数组中失败的promise对象的结果
“第一个完成”:同步任务先完成,异步任务后完成
二、promise的几个关键问题
1. 如何改变promise的状态?
(1)resolve函数,pending变resolved
(2)reject函数,pending变rejected
(3)抛出异常,pending变rejected
let p = new Promise((resolve, reject) => {
// 1.resolve函数
// resolve('ok'); //pending => resolved(fulfilled)
// 2.reject函数
// reject('error'); //pending => rejected
// 3.抛出错误
throw '出问题了';
});
console.log(p);
2. 一个promise指定多个成功/失败的回调函数,都会调用吗?
(指定回调用then、catch方法)
当promise改变为对应状态时都会调用
let p = new Promise((resolve, reject) => {
resolve('ok');
});
// 指定回调-1
p.then(value => {
console.log(value);
});
// 指定回调-2
p.then(value => {
alert(value);
});
3. 改变promise状态和指定回调函数谁先谁后?
指定回调函数 不是 执行回调函数
因为then是微任务,setTimeout是宏任务,所以同步任务执行完以后先去执行then,也就是指定了p改变状态时的回调函数,然后再执行定时器,执行了resolve('ok'),触发then里的回调
(1)都有可能,正常状态下是先指定回调再改变状态(执行器中是异步任务的情况居多),但也可以先改变状态再指定回调
(2)如何先改变状态再指定回调?
a.在执行器中是个同步任务,直接调用resolve()/reject()
b.延迟更长事件才调用then(),即定时器延迟时间更长
(3)什么时候才能得到数据?即回调什么时候执行
a.如果先指定的回调,那当状态发生改变以后,回调函数才会调用,得到数据
b.如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据
4. promise.then() 返回的新promise的结果状态由什么决定?
(1)简单表述:由then()指定的回调函数执行的结果(返回值)决定
(2)详细表述:
a.如果then的回调函数抛出异常,新promise变为rejected,reason为抛出的异常
b.如果返回的是非promise的任意值,新promise变为resolved,value为返回的值
c.如果返回的是另一个新promise,此promise的结果就会成为新promise的结果
let p = new Promise((resolve, reject) => {
resolve('ok');
});
let result = p.then(value => {
// console.log(value);
// 1.抛出错误
// throw '出了问题';
// 2.返回结果是非promise类型的对象
// return 123;
// 3.返回结果是Promise对象
return new Promise((resolve, reject) => {
reject('出错了')
})
}, reason => {
console.warn(reason);
});
console.log(result);
5. promise如何串联多个操作任务?
promise的then()返回一个新的promise,可以通过then的链式调用串联多个同步/异步任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 1000)
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve('success');
})
}).then(value => {
console.log(value); //success
}).then(value => {
console.log(value); //undefined
})
6. promise异常穿透?
当使用promise的then链式调用时,可以在最后指定失败的回调。前面任何操作出了异常,都会传到最后失败的回调中处理
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
// reject('error');
}, 1000)
});
p.then(value => {
// console.log(111);
throw '失败啦';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason); //失败啦
})
7. 中断promise链?
当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
办法:在回调函数中返回一个pending状态的promise对象
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 1000)
});
p.then(value => {
console.log(111);
// 有且只有一种方式中断promise链条
return new Promise(() => { }); //pending状态,没有改变状态,后面then无法执行
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
})
三、async 与 await
1. async 函数
函数的返回值为promise对象
async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。
该promise对象的结果由async函数执行的返回值决定
和then的返回结果的规则一样
async function main() {
// 1.如果返回值是一个非Promise类型的数据
// return 1351;
// 2.如果返回的是一个Promise对象
// return new Promise((resolve, reject) => {
// reject('ERROR');
// });
// 3.抛出异常
throw 'Error'
}
let result = main();
console.log(result);
2. await 表达式
await
操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。
await右侧的表达式一般为promise对象,但也可以是其他类型的值
如果表达式是promise对象,await返回的是promise成功的值
如果表达式式其他的值,直接将此值作为await的返回值
注意:
await必须写在async函数中,但async函数中可以没有await
如果await的promise失败了,就会抛出异常,需要通过try...catch捕获处理