所谓“回调地狱”,相信不少用JS写异步的同学都见识过,一层又一层的函数嵌套,运行是没问题的,可是维护的时候却相当可怕。
举个最简单的例子:
function join(){
if(true){
function joinAgain(){
return function item(){
return 'done'
}
}
}else{
if(false){
function joinSecond(){
function joinThird(){
function done(){
return 'done'
}
}
}
}
}
}
多重‘}’
结尾,我们甚至很多时候如果没有IDE的提示都很难找到作用域,更别说二次维护的时候很容易出bug了,因此在代码编写中,尽可能地避免回调地狱,是非常重要的事情,所幸es6提供了Promise
对象,帮助我们尽可能地回避这种困扰。
一、什么是Promise
简单来说,Promise
是一个容器,里面可以用来保存某个还没开始,或将要完成的事件结果,将一些需要异步的操作放进Promise对象
中,然后代码继续往下执行,当需要的时候才从Promise
中获取到异步操作的结果或进行所需操作。
二、如何使用Promise
在阮一峰大神的es6文档中有个简单是示例:
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
但是我个人认为,第一次看反而不好理解,最好结合实际的例子来举例,比如说设置一个需要异步操作的函数,利用Promise来返回结果:
function timeout(ms) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.log('先执行我')
let msg = '成功后才执行我';
let error = '失败后才执行我'
let result = true
if(result){resolve(msg)}else{reject(error)}
}, ms,);
});
}
timeout(100).then(
function(a){
console.log(a)
},function(b){
consloe.log(b)
}
);
//输出顺序:
//'先执行我'
//'成功后才执行我'
这里模拟了一个异步的操作,100毫秒之后才执行timeout
函数,然后等后面所有代码执行完之后,再执行它之后的resolve
或reject
函数(这里不知道resolve
和reject
也没关系,后面会介绍。)
三、详解Promise
还是以上面的timeout
作为例子讲解:
function timeout(ms) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
console.log('先执行我')
let msg = '成功后才执行我';
let error = '失败后才执行我'
let result = true
if(result){resolve(msg)}else{reject(error)}
}, ms,);
});
}
timeout(100).then(
function(a){
console.log(a)
},function(b){
consloe.log(b)
}
);
关于Promise对象的结构,我们只需要了解这几点就能看明白它的运作机制:
- Promise对象接收一个函数作为参数,如上面的:
return new Promise(function(resolve, reject) {})
; - 同时,Promise里面的函数又接收两个函数作为参数,分别是
resolve
和reject
(这两个函数的名字不是固定的,可以自定义,但是一般地,我们约定俗成使用resolve
来代表完成后执行的方法,用reject
表示出错时执行的方法。); resolve
表示异步成功时要做的事,它传入的参数msg
会直接传到.then
中,同理,reject
中传入的参数error
也会传到.then
当中;.then
方法是用来指定该Promise对象
成功或失败时要做的事,里面接收两个回调函数作为参数,也就是在Promise实例
生成时传入的resolve
方法和reject
方法,但是在.then
中,我们不用按原本的函数名写进去;- 在
.then
当中,默认第一个传入的回调函数,就是代表resolve
的,所以上面的function(a){}
表示的就是resolve
,里面的参数a
也就是resolve(msg)
中的msg
; .then
中的第二个函数默认代表reject,表示出错时执行的函数,也就是里面的function(b){}
,同理,b
也就是reject(error)
中的error
;- 第二个回调函数不是必须要传的!
总结:
所以整个timeout
函数实际上是个异步操作,它在100ms之后执行了第一个console
,如果此时result
为true
(也就是假设异步操作成功)时,那么整个Promise实例就会被标记为resolved
状态,那么在所有代码都运行完之后,它就会去.then
方法中执行resolve
对应的方法。
四、关于Promise的状态
从上面的总结我们知道,每个Promise实例是根据它里面的异步操作结果来标记自身状态的,然后根据不同状态再作出.then
中执行resolve
还是reject
。
Promise总的来说,有三种状态:
并且状态时不可逆的! 这就说明,如果Promise中的异步出错了,那么我们只能捕获错误并进行处理,而无法通过某些操作使它变回成功然后执行resolve方法。
五、Promise的错误处理
一般来说,我们在Promise中定义的reject(e)
足以用来捕获错误,除此之外,我们还可以在用.catch()
方法进行捕获,如:
promise.then((data) => {return false})
.catch((e) => {console.log(e)}//参数e也是通过reject传递的
如果异步操作抛出错误,状态就会变为rejected
,就会调用catch
方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。