Promise和async/await(一)
为什么会有同步和异步?
首先JS 是单线程的,单线程程序在执行的时候,所有的程序都是按照顺序执行的,前面的必须处理好后面的才会执行。
JS 是单线程的,但是浏览器加载一些需要网络请求的资源,ajax或者执行一段 setTimeout代码,由于是单线程,要等这些内容访问或者执行完才执行下面的代码,那么你发送ajax请求,执行setTimeout 的这段时间什么也做不了,这种效果对程序是一种堵塞(同步堵塞),这个时候异步就出现了,在涉及需要等待的操作,我们把代码交给其他对应的浏览器线程去执行,在执行结束的时候,通知我们的主线程执行完毕,你可以操作资源了,这段等待时间并不影响你程序的执行,只是在未来的某个时间段(不确定),有一个操作一定执行,这就是异步(异步非阻塞)
回调
异步和同步相比,最难掌控的就是异步任务会什么时候完成和完成之后的回调问题。
回调是异步编程最基本的方法
像下面的例子
listen( "click", function handler(evt){
setTimeout( function request(){
ajax( "http://some.url.1", function response(text){
if (text == "hello") {
handler();
}
else if (text == "world") {
request();
}
} );
}, 500) ;
} );
console.log('doSomething')
这种地域式的回调,令代码的可读性非常差!!
信任问题
在你不知道的javascript一书中,对于回调的信任问题做了阐述 当你使用第三方的库的方法处理回调时很有可能遇到以下信任内容
· 调用回调的过早
· 调用回调过晚
· 调用回调次数过多会过少
· 没有把所需要的环境/参数成功的传给你的回调函数
· 吞掉可能出现的错误或异常
· .......
那么怎么解决这种信任问题呢?
你需要一个承诺
当你把一件事情交给别人去做的时候,这个任务可能马上完成也可能一段时间后完成,这个人在任务完成或者失败的时候回给你一个回应,放心的人!!回应就表示成功了或者失败了,没回应就表示正在执行~~~
Promise(承诺) 就是这样人
【官方】Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象
三种状态:
1.pending(进行中)
2.fulfilled (已成功)通常也称为 resolved
3.rejected (已失败)
特点
1.对象状态不受外界影响。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
2.一旦改变状态,就不会在改变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected
缺点
1.无法取消Promise,一旦新建它就会立即执行,无法中途取消。
2.如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
3.当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
我们看一个简单的promise例子
Promise构造函数接受一个函数,该函数有两个参数 resolve 和 reject,他们也是两个函数。
resolve函数的作用是:将Promise 的状态从“未完成”变成“成功”(即从“pending”变成“resolved”),在异步操作成功的时调用,并将一部操作的结果,作为参数传递出去。
reject函数的作用是:将Promise 的状态从“未完成”变成“失败”(即从“pending”变成“rejected”),在异步操作成功的时调用,并将一部操作的结果,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
let promise = new Promise((resolve,reject)=>{
// 接收一个callback。参数是成功函数与失败函数
setTimeout(()=>{
let num = parseInt(Math.random()*100);
// 如果数字大于50就调用成功的函数,并且将状态变成Resolved
if(num > 50){
resolve(num);
}else{
// 否则就调用失败的函数,将状态变成Rejected
reject(num)
}
},3000)
})
当Promise执行的内容符合你预期的成功条件的话,就调用resolve函数,失败就调用reject函数,这两个函数的参数会被promise捕捉到。可以在之后的回调中使用
创建一个承诺完成了,我们如何使用它呢
promise.then(res => {
//在构造函数中如果你执行力resolve函数就会到这一步
console.log(res)
}, err => {
// 执行了reject函数会到这一步
console.log(err);
})
Promise.prototype.then()
then方法接収两个函数,第一个是承诺成功(状态为resolved)的回调函数,第二个(可选)是承诺失败(状态为rejected)的回调函数。
then方法的返回值不是一个promise对象就会被包装成一个promise对象,所以then方法支持链式调用。
then方法的可以帮我们串行的解决一些逻辑问题,让我们的书写更加顺畅。
看看下面这个
ajax('first');
ajax('second');
ajax('third');
需要按顺序来执行怎么办?
ajax('first').success(function(res){
ajax('second').success(function(res){
ajax('third').success(function(res){
//串行完毕可以执行你想要的内容了
});
})
})
上面地狱式的回调,可怕!!如果使用下面的 then 链式调用,就会好很多
let promise = new Promise((resolve,reject)=>{
ajax('first').success(function(res){
resolve(res);
})
})
promise.then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
return new Promise((resovle,reject)=>{
ajax('second').success(function(res){
resolve(res)
})
})
}).then(res=>{
// 串行完毕你要做的xxx可以开始了
})
串行说完了,那并行的怎么办,当我们有多个异步事件,之间并没有先后顺序,只需要全部完成就可以开始工作。
这个时候我们可以使用 Promise.all
Promise.all()
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
1.只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
2.只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
var p1 = new Promise((resolve, reject) => {
resolve('p1')
})
var p2 = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('p1')
}, 1000)
})
var p3 = new Promise((resolve, reject) => {
resolve('p3')
})
Promise.all([p1, p2, p3])
.then(val => {
console.log('then', val)
})
.catch(err => {
console.log('catch', err)
})
// then (3) ["p1", "p1", "p3"]
var p1 = new Promise((resolve, reject) => {
resolve('p1')
})
var p2 = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('p1')
}, 1000)
})
var p3 = new Promise((resolve, reject) => {
reject('p3')
})
Promise.all([p1, p2, p3])
.then(val => {
console.log('then', val)
})
.catch(err => {
console.log('catch', err)
})
// catch p3
注意: 若实例自带了 .then() 则先执行实例的 then 在执行 all的then;若实例自带了 .catch 则先调用 实例的 catch 再执行 .then()。
var p1 = new Promise((resolve, reject) => {
resolve('p1')
}).then(val => {
console.log(555, val)
})
var p2 = new Promise((resolve, reject) => {
setTimeout(()=> {
resolve('p1')
}, 1000)
})
var p3 = new Promise((resolve, reject) => {
resolve('p3')
})
Promise.all([p1, p2, p3])
.then(val => {
console.log('then', val)
})
.catch(err => {
console.log('catch', err)
})
Promise.race()
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.prototype.catch()
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
var p = new Promise((resolve, reject) => {
//此处抛出异常,状态就会变成rejected,就会调用catch() 方法的回调函数,处理这个错误。
throw new Error("测试错误test!!!")
})
p.catch((err) => {
console.log(err) // Error: 测试错误test!!!
})
等同于
// 写法一
var p = new Promise((resolve, reject) => {
throw new Error("测试错误test!!!")
})
p.then(null, (err) => {
console.log(err)
})
// 写法二
var p = new Promise((resolve, reject) => {
reject(new Error("错误测试test!!!!"))
})
p.catch((err)=> {
console.log(err)
})
// 写法三
var p = new Promise((resolve, reject) => {
try {
throw new Error("错误测试test!!")
} catch(err) {
reject(err)
}
})
p.catch((err)=> {
console.log(err)
})
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法比较好。
// 不建议使用
var p = new Promise((resolve, reject) => {
throw new Error("test-error")
})
p.then(val=> {
console.log(1212)
}, error => {
console.log(666, error);
})
// 建议使用
var p = new Promise((resolve, reject) => {
throw new Error("test-error")
})
p.then(val=> {
console.log(1212)
}).catch(error=> {
console.log("error", error)
})
Promise.prototype.finally()
不管Promise最后状态如何,都会执行 finally() 。
finally() 方法的回调函数不接受任何参数。这也就意味着finally中无法知道 Promise 的状态。这表明,finally里面的操作应该和状态无关,不依赖与Promise 的执行结果。
finally的本质是 then 方法的特例。
finally 方法不一定是最后一环,后面还可以在跟 .then() ,返回的是一个Promise
var p = new Promise((resolve, reject)=> {
throw new Error("test~~")
})
p.then((val)=> {
console.log(11, val)
}).finally(()=> {
console.log('finally')
}).catch((err)=> {
console.log(err)
})
// finally
// Error: test~~
还有
Promise.allSettled()
Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。
Promise.any()
ES2021 引入了Promise.any()
方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。只要参数实例有一个变成fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。
注意:
all(), race(), allSettled(), any() 都是 Promise 的方法;
then(), finally(), catch() 都是Promise实例的方法挂载在Promise.prototype上;