在了解promise之前,我们先学习几个概念
1、回调函数
当一个函数作为参数传入另一个参数中,它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。我们熟悉的定时器中就存在有回调函数:
function fn(){
console.log('你好,anni');
}
setTimeout(fn,2000)
只有在满足2秒之后才会执行 fn 函数中的内容,此时 fn 就是回调函数。
在了解了回调函数之后,与之相对应的概念是“同步任务”,“异步任务”
同步任务
在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。
异步任务
不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行(没有顺序的)。来看一个异步的例子
setTimeout(function(){
console.log('你好,anni');
},2000)
console.log('你好,tony');
打印结果中可以显示,异步操作没有顺序,不阻塞后续任务的执行。
2、回调地狱
在回调函数中嵌套回调函数的情况叫做回调地狱。回调地狱是为实现代码顺序执行
而出现的一种操作。
setTimeout(function(){
console.log('你好,anni');
setTimeout(function(){
console.log('你好,tony');
setTimeout(function(){
console.log('你好,lin');
},1000)
},2000)
},3000)
按照顺序打印出结果
通过上面的例子我们也可以看出问题,如果在业务操作过程中我们需要嵌套很多层的话,那么回调地狱就会出现问题。
总结回调地狱会出现的问题:
1、嵌套层次很深,可读性差,难以维护
2、无法正常使用 return 和 throw
3、无法正常检索堆栈信息
4、多个回调难以建立联系
也正是因为回调地狱所产生的问题,那么 primise 对象就是为了解决回调地狱问题而诞生
3、promise概念
Promise是js中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案(回调地狱)。
它通过引入一个回调,避免更多的回调
。
简单说Promise就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。
从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
3.1 Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
let p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('你好呀朋友');
resolve('你好');
}, 2000);
});
3.2 注意
1、Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。它们是两个函数,又是JavaScript引擎提供,不是自己部署。resolve函数的作用:将Promise对象的状态从“未完成”变成“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
2、Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。
3、Promise的链式编程可以保证代码的执行顺序,前提是每一次在then做完处理后,一定要return一个Promise对象,这样才能在下一次then时接收到数据。
看一个使用promise对象写出上一个例子的代码吧!
function fn(str){ //str = '你好,anni'
let p = new Promise(function(resolve,reject){
let flag = true
setTimeout(function(){ //模拟异步调用
if(flag){
resolve(str)
}else{ //resolve('你好,anni')
reject('操作失败')
}
})
})
return p;
}
let tmp = fn('你好,anni');
tmp.then((data)=>{ //data = '你好,anni'
console.log(data); //data = "你好,tony"
return fn('你好,tony')
}).then((data)=>{
console.log(data); //data = '你好,linda'
return fn('你好,linda')
}).then((data)=>{
console.log(data);
}).catch((err)=>{
console.log(err);
})
我们可以得到结果,但Promise虽然跳出了异步嵌套的怪圈,用链式表达更加清晰,但是我们也发现如果有大量的异步请求的时候,流程复杂的情况下,会发现充满了屏幕的then,看起来非常吃力,这时候,ES7的async/await方法的出现就是为了解决这种复杂的情况。
4、使用async/await基本规则
- await关键字只能在使用async定义的函数中使用
- await后面可以直接跟一个 Promise实例对象(可以跟任何表达式,更多的是跟一个返回Promise对象的表达式)
- await函数不能单独使用
- await可以直接拿到Promise中resolve中的数据。
- async/await的作用就是在异步调用中展现同步的效果。
还是继续看上面的例子使用async的写法
function fn(str){ //str = '你好,anni'
let p = new Promise(function(resolve,reject){
let flag = true
setTimeout(function(){ //模拟异步调用
if(flag){
resolve(str)
}else{ //resolve('你好,anni')
reject('操作失败')
}
})
})
return p;
}
async function test(){
let s1 = await fn('你好,anni');
let s2 = await fn('你好,tony');
let s3 = await fn('你好,linda');
console.log(s1,s2,s3);
}
test();
这样就很好的解决了多条then语句的问题,await等待的虽然是promise对象,但不必写.then(…),直接可以得到返回值。
可是我们也能发现,上面展示代码是成功打印呈现的代码,然后当我们令flsg = false 时,会得到错误的结果。
这时候就需要使用try…catch 方式捕获异步的传递操作失败的信息。还是看代码喽!
function fn(str){ //str = '你好,anni'
let p = new Promise(function(resolve,reject){
let flag = false
setTimeout(function(){ //模拟异步调用
if(flag){
resolve(str)
}else{ //resolve('你好,anni')
reject('操作失败')
}
})
})
return p;
}
async function test(){
try{
let s1 = await fn('你好,anni');
let s2 = await fn('你好,tony');
let s3 = await fn('你好,linda');
console.log(s1,s2,s3);
}catch(error){
console.log(error);
}
}
test();
此时,针对异步传递操作失败就能显示正确的信息啦!
promise 和 async/await 的区别
1、promise是ES6, async/await 是ES7 ,promise为了弥补回调地狱的不足, async/await 为了弥补primise 中的不足。
2、 async/await 相对于promise 来讲,写法更加优雅,更简洁。
3、reject状态:
1)promise 错误可以通过catch来捕捉,建议尾部捕获错误
2) async/await 既可以用.then 又可以用 try-catch 捕获