在了解Promise之前需要了解js程序执行机制.
我们都知道 JS 是一个单线程的语言,永远只有一个通道在运行程序。通常来说就是按书写顺序执行,先执行程序A,再执行程序B,这样就会引出一个问题
假如我有个图片资源请求,请求时间很长,那么就会造成下面的代码就要等这个图片资源请求完成之后再执行下一步程序,在这个过程中其他的程序不会被执行,这样是很不友好的.
还好程序员发明了异步和同步这两种方式来处理问题
异步执行的好处
程序异步运行,可以提高程序运行的效率,不必等一个程序跑完,再跑下一个程序,特别当这两个程序是无关的时候。
模拟 JS 中异步的方法 —— setTimeout
function fn(){
console.log('1');
console.log('2');
}
function fn2() {
setTimeout(()=>{
console.log('3');
},1000)
console.log('4');
}
fn()
fn2()
//1
//2
//4
//3
这个例子中我们可以看到,我们定义了两个函数,fn()和fn2(),这里需要注意的是,fn()和fn2的书写顺序,不管函数声明顺序是怎样,只管函数的调用顺序.上面的代码是先调用fn()执行1,2
再调用fn2(),这里先打印出4,再打印出3,这是因为setTimeout设置了1秒之后执行
除了setTimeout可以模拟异步操作之外,回调函数也可以模拟异步,
function printAsync(str, callback) {
console.log(str);
if (callback) setTimeout(callback, 1000);
}
printAsync('aaa', function () {
printAsync('bbb', function () {
printAsync('ccc', function () {
printAsync('ddd');
})
})
});
//aaa
//bbb
//ccc
//ddd
看到这么多的})是不是有点烦躁?这还是相对简单的,没什么复杂的业务逻辑,像这种多个回调函数的多层嵌套会让代码的可读性直线下降,因此又称为回调地狱。
Promise
Promise是一种处理异步的方式,解决了函数的异步回调问题,在通常情况下,如果使用多级回调函数,代码很难维护,这样就衍生了一种新的方式更好的处理这个问题.Promisse还有个好处就是捕捉错误信息.
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
我们通过 一个例子来看看Promise的具体用法
let ajax = function() {
console.log('执行');
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve()
},1000)
})
}
ajax().then(()=>{
console.log('promise'+':'+'timeout1');
})
//执行
//promise timeout1
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
Promise还可以通过catch来捕获错误,我们来看下代码的实现
let ajax = function(num){
console.log('开始执行');
return new Promise(function(resolve,reject){
if(num > 5){
resolve()
}else{
throw new Error('出错了')
}
})
}
ajax(1).then(()=>{
console.log('log',6);
}).catch((err)=>{
console.log('catch',err);
})
ajax(6).then(()=>{
console.log('log',6);
}).catch((err)=>{
console.log('catch',err);
})
如果出现错误,可以得知哪一步出错,可以帮助我们解决bug
除了这些还有一些高级用法,
function loadImg(src){
return new Promise((resolve,reject)=>{
let img = document.createElement('img')
img.src = src
img.onload = function(){
resolve(img)
}
img.onerror = function(err){
reject(err)
}
})
}
function showImgs(imgs){
imgs.forEach(function(img){
document.body.appendChild(img)
})
}
Promise.all([
loadImg('https://cdn.pixabay.com/photo/2018/06/10/00/11/seagull-3465550__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/10/21/29/white-currant-3467373__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/12/03/41/butterbur-3469942__340.jpg'),
]).then(showImgs)
通过Promise.all(),可以等到三张图片数据全部加载完成之后再渲染到页面上
当然还有一种高级用法
function loadImg(src){
return new Promise((resolve,reject)=>{
let img = document.createElement('img')
img.src = src
img.onload = function(){
resolve(img)
}
img.onerror = function(err){
reject(err)
}
})
}
function showImgs(img){
document.body.appendChild(img)
}
Promise.race([
loadImg('https://cdn.pixabay.com/photo/2018/06/10/00/11/seagull-3465550__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/10/21/29/white-currant-3467373__340.jpg'),
loadImg('https://cdn.pixabay.com/photo/2018/06/12/03/41/butterbur-3469942__340.jpg'),
]).then(showImgs)
Promise.race的用法就是,只要Promise状态改变,有数据到来,我就把第一条到来的数据渲染到页面上,其他的不再响应。