持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
浏览器事件循环
要说Promise就得先了解浏览器的事件循环,也就是浏览器的执行顺序。
浏览器是单线程的语言,也就是当浏览器在解析js代码时,会将代码放在全局上下文执行栈中,在栈中执行这个js代码时,如果是同步的代码,浏览器就会执行这段代码,但是如果这段代码是异步的,那么浏览器就会将这个异步的代码放入一个队列中,等到执行栈中的所有代码都执行完毕后,浏览器又会从队列中取出一个异步的代码放入执行栈中,再执行。一直循环这个操作。
console.log('aaa');
setTimeout(()=>{
console.log('bbb')
},1000);
console.log('ccc');
// 'aaa'
// 'ccc'
//等待1秒后 'bbb'
Promise
Promise是ES6之后才加的,它主要为了解决的问题就是之前的回调地狱,也更利于代码维护和阅读。
回调地狱:
setTimeout(function () { //第一层
console.log('aaa');
setTimeout(function () { //第二程
console.log('bbb');
setTimeout(function () { //第三层
console.log('ccc');
}, 1000)
}, 2000)
}, 3000)
就是我们之后要执行的代码要依赖于之前的代码执行结果,那么我们就得嵌套很多回调函数,如果业务一复杂,就会导致这个代码变得很复杂,也很难维护,可读性也差。
而Promise就是为了解决这个问题的。他将多层回调改成了链式调用。
const fn = function(params,delay=1000) {
let p = new Promise((resolve,reject) => {
setTimeout(()=>{
resolve(params)
},delay)
});
return p;
}
fn('aaa',3000).then(data =>{
console.log(data);
return fn('bbb',2000)
}).then(data => {
console.log(data);
return fn('ccc')
}).then(data => {
console.log(data)
})
Promise是同步还是异步?
我们看到了Promise的用处,下面我们就总结Promise的用法。
Promise里面的代码是同步的
let p = new Promise((resolve,reject) => {
console.log('aaa');
resolve('ok')
})
console.log('bbb');
// 代码输出
我们发现,是先输出aaa再输出bbb的,也就是说Promise里面是同步的。
resolve与reject
我们发现在Promise里面的回调函数中有两个参数,一个是resolve,一个是reject。
如果我们不再Promise中使用resolve或者reject:
let p = new Promise((resolve,reject)=>{})
console.log(p);
我们发现p是 <pending> 状态。
如果我们使用resolve:
let p = new Promise((resolve,reject) => {
resolve('ok')
})
console.log(p);
我们发现p是 <fullfilled> 状态,且PromiseResult中的值为我们传的参数‘ok’。
如果我们使用reject:
let p = new Promise((resolve,reject) => {
reject('no')
})
console.log(p);
我们发现p是 <rejected> 状态,且还有一个报错,且PromiseResult中的值为我们传的参数‘no’。。
从上面的三个输出我们就可以得出结论:resolve()是返回成功的结果,reject()是返回失败的结果。
Promise的状态一旦改变了,就不可再被改变
Promise有三种状态,初始时是 <pending> ,成功后为<fullfilled>,失败后为<rejected>。
那么如果Promise已经被改变为fullfied,还可以再被改变为rejected吗?
答案是不可以:
let p = new Promise((resolve,reject) => {
resolve('ok');
console.log('aaa')
reject('no')
})
console.log(p);
我们可以发现,虽然在resolve以后,代码还可以执行,但是再reject后,promise的状态并没有被改变。
then
在Promise中,我们resolve或者reject后,就返回了一个Promise实例,但是我们想得到resolve或者reject中的值,然后继续执行怎么办呢?那就得靠then了
then有两个参数
then有两个参数,一个是处理resolve的函数,一个是处理reject的函数。
let p = new Promise((resolve,reject) => {
reject('ok')
})
p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
// 'ok'
let p = new Promise((resolve,reject) => {
reject('no')
})
p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
// 'no'
也就是说,第一个参数函数是处理<fullfied>状态的Promise,且函数里面的参数就是Promise的PromiseResult。第二个参数是处理<rejected>状态的Promise,且函数里面的参数就是Promise的PromiseResult。
then是同步还是异步?
我们知道Promise里面的回调函数是同步的,那么then里面的回调函数是同步还是异步的呢?
let p = new Promise((resolve,reject) => {
reject('ok')
})
p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
console.log('aaa');
我们发现,先输出的是'aaa',在输出的是'ok',所以then里面的回调函数是异步的。
then也有返回值
let p = new Promise((resolve,reject) => {
reject('ok')
})
p = p.then(res => {
console.log(res)
}, err => {
console.log(err)
})
console.log(p);
我们发现,then里面也是有返回值的,哪怕我们并没有写return。它默认返回的是一个<fullfilled>状态的Promise实例,返回的值是undefined。
如果我们返回一个常量:
let p = new Promise((resolve,reject) => {
resolve('ok')
})
p = p.then(res => {
console.log(res);
return 'okk'
}, err => {
console.log(err);
return 'noo'
})
console.log(p);
如果我们返回的是其他原始值,返回的是一个<fullfilled>状态的Promise实例,返回的值是我们return 里面的值。
我们知道我们可以使用then来调用Promise实例。
let p = new Promise((resolve,reject) => {
resolve('ok')
})
p = p.then(res => {
console.log('resolve:'+res);
return 'okk'
}, err => {
console.log('reject:'+err);
return 'noo'
})
p.then(res => {
console.log('resolve:'+res);
return 'okkk'
}, err => {
console.log('reject:'+err);
return 'nooo'
})
let p = new Promise((resolve,reject) => {
reject('no')
})
p = p.then(res => {
console.log('resolve:'+res);
return 'okk'
}, err => {
console.log('reject:'+err);
return 'noo'
})
p.then(res => {
console.log('resolve:'+res);
return 'okkk'
}, err => {
console.log('reject:'+err);
return 'nooo'
})
我们发现的确是这样的!不管是在resolve还是在reject中返回的都是默认的<fullfilled>状态。
then里面返回Promise
我们知道如果我们返回的不是Promise,那么都默认返回的是<fullfilled>状态,那么如何返回其他状态的呢?
我们可以return一个Promise实例,这个return的Promise是哪种状态,最终返回的就是哪种状态:
let p = new Promise((resolve,reject) => {
reject('no')
})
p = p.then(res => {
console.log('resolve:'+res);
return Promise.reject('noo')
}, err => {
console.log('reject:'+err);
return Promise.resolve('okk')
})
p.then(res => {
console.log('resolve:'+res);
}, err => {
console.log('reject:'+err);
})
catch
我们可以对上面的链式调用进行简写,就是使用catch来统一对<rejected>状态进行管理,前面出的任何错误,都会跳在这里进行处理。
let p = new Promise((resolve,reject) => {
reject('no')
})
p.then(res => {
console.log('resolve:'+res);
return Promise.reject('noo')
}).then(res => {
console.log('resolve:'+res);
}).catch((err) => {
console.log('err:'+err)
})
let p = new Promise((resolve,reject) => {
resolve('yes')
})
p.then(res => {
console.log('resolve:'+res);
return Promise.reject('noo')
}).then(res => {
console.log('resolve:'+res);
}).then(res => {
console.log('resolve:' + res)
}).catch((err) => {
console.log('err:'+err)
})
输出:
我们发现是直接跳转到最后了,中间的部分就会被跳过!
除此之外,catch还可以捕获这个链式调用中的错误,然后让代码继续运行,不至于堵塞。
let p = new Promise((resolve,reject) => {
resolve('yes')
})
p.then(res => {
console.log('resolve:'+res);
console.log(a);
return Promise.reject('noo')
}).then(res => {
console.log('resolve:'+res);
}).catch((err) => {
console.log('err:'+err)
})
我们发现在第一个then里面,我们输出了一个变量a,但是a并没有被定义,如果我们不对这个错误进行捕获,那么这段代码就会卡在错误那里,不再继续往下执行。
功能等同于try/catch.
catch也是有返回值的
let p = new Promise((resolve,reject) => {
resolve('yes')
})
p = p.then(res => {
console.log('resolve:'+res + a);
}).then(res => {
console.log('resolve:'+res);
}).catch((err) => {
console.log('err:'+err)
})
console.log('pp:'+p);
输出:
返回的也是默认的<fullfilled>状态的Promise实例。所以也还可以继续往下进行then调用。
Promise.all()
Promise.all()接收一个参数,这个参数就是数组,数组里面的元素都得是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理.
let p = Promise.all([p1,p2,p3]);
p的状态由p1、p2、p3决定,分成两种情况:
- 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
let p1 = new Promise((res,err) => {
setTimeout(()=>{
res('p1')
},3000)
});
let p2 = new Promise((res,err) => {
setTimeout(()=>{
res('p2')
},2000)
})
let p3 = new Promise((res,err) => {
setTimeout(()=>{
res('p3')
},1000)
})
let p = Promise.all([p1,p2,p3]);
p.then(data => {
console.log(data)
},err => {
console.log(err)
})
我们发现只有等3s后,才会输出['p1','p2','p3']。
Promise.race()
类似Promise.all,但是区别就是Promise.all是要将参数中所有的实例都返回状态后,Promise.all的状态才会改变。而Promise.race是有任意一个完成,那么这个Promise.race的状态就会改变.
let p1 = new Promise((res,err) => {
setTimeout(()=>{
res('p1')
},3000)
});
let p2 = new Promise((res,err) => {
setTimeout(()=>{
res('p2')
},2000)
})
let p3 = new Promise((res,err) => {
setTimeout(()=>{
res('p3')
},1000)
})
let p = Promise.race([p1,p2,p3]);
p.then(data => {
console.log(data)
},err => {
console.log(err)
})
1S后输出了'p3',而Promise.all是3S后输出。