Promise 是异步编程的一种解决方案,比起传统的解决方案——回调函数和事件,它更合理且更强大
Promise 简单来说就是一个容器,里面放着某个未来才会结束的事件的结果(通常是一个异步操作的结果)。比如说你在考试,突然发现你打的小抄可以派上用场了。此时你打小抄有两种后果,安然无恙抄的很舒服,或者被监考老师发现作弊处理,无论结果是什么,舒服还是难受就相当于一个Promise 对象(举的可能不恰当)
let promise = new Promise(function(resolve, reject){
let problem = new Problem(); // 你不会做的题目
if( solve(problem) ) return resolve('solve'); // 尝试作弊 作弊成功 舒服 答案就是solve
else return reject(new Error('not solve')) // 卧槽 失败!被老师抓到 这回not solve
});
promise.then(function(value){
// value 就是答案内容
}).catch(function(err){
// err 就是你准备去办公室 是打还是骂还是请家长 看老师怎么操作
})
语法 new Promise(function(resolve, reject) { ... } );
它有两个特点,一是它代表的是一个异步的操作,有三种状态:Pending(进行中),Fulfilled(已成功)和 Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这种状态
二是状态一旦改变就不会再次改变,并且再次调用的时候回立即获得结果。有两种状态改变的可能:从 Pending 变为 Fuilfilled 或者从 Pending 变为 Rejected。状态变化以后就处于 Resolved(已定型)的阶段。这就相当于你作弊失败,还能当成没看到吗?除非你跟老师有一腿
假设有这么一个场景,你需要向后台请求三个字符串,然后需要等到这三个请求的结果都返回然后拼接字符串在进行相关的操作,你该怎么做?
可能你会在 ajax 请求里面的回调里面发起第二个请求,然后第二个请求的回调发起第三个请求,最后在第三个请求的回调里面进行相关操作。是不是想一想就觉得写起来很别扭,而用 Promise 的话就很好解决了
var result = Promise.all([ajax1,ajax2,ajax3]).then(()=>{
})
基本用法
let promise = new Promise(function(resolve, reject){
// todo
if(/*异步操作成功*/){
resolve(value);
} else {
reject(error);
}
})
// 等同于下面这种方式,因为既然想了解 Promise 的话应该对箭头函数有所了解,所以后面会采用下面这种方式。
let promise = new Promise((resolve, reject)=>{
// todo
if(/*异步操作成功*/){
resolve(value);
} else {
reject(error);
}
})
resolve 的作用将 Promise 对象的状态从 “未完成” 变成 “成功”,并将成功时的结果作为参数传出去。
reject 的作用是将 Promise 对象的状态从 “未完成” 变成 “失败”。并将失败的结果作为参数传出去
var bstep=true
let promise=new Promise((resolve,reject)=>{
if(bstep){
resolve('123')
}else{
reject('456')
}
})
promise.then(value=>{
console.log(value)
}).catch(err=>{
console.log(err)
})
如果bstep=true 输出123,反之输出456
当我们新建Promise实例的时候,此时的状态是pending(初始状态),经过resolve之后Promise对象的状态就变成了成功(Fulfilled),此时就可以执行then方法;reject将Promise对象的状态变成失败(Rejected),用catch捕捉
这里要注意的是,Promise对象中的代码会立刻执行
let promise=new Promise((resolve,reject)=>{
console.log('立即执行')
var time=setTimeout(function(){
resolve('yean!')
},200)
})
promise.then(value=>{
console.log(value)
}).catch(err=>{
console.log(err)
})
先打印立即执行再打印yean,看过我异步事件宏任务微任务的应该有印象
其实then方法附带了失败函数
promise.then(val => {
// success
},err =>{
// fail
})
但我们不建议使用第二个参数,还是用catch捕捉reject
写个异步加载图片的demo
先封装一个方法
function AddImageAsync(url){
let promise = new Promise((resolve,reject) =>{
var img = new Image();
img.src = url;
img.onload = resolve(img);
img.onerror = reject(new Error(url+'此地址失效')); //onerror方法使用img标签
});
return promise;
}
接下来我们传入一张图片地址
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="div"></div>
</body>
<script>
let url = 'http://p0.so.qhimgs1.com/bdr/_240_/t0199b2fbed323908da.jpg';
let loadImg = AddImageAsync(url)
loadImg.then((img)=>{
div.appendChild(img);
console.log('success');
});
function AddImageAsync(url){
let promise = new Promise((resolve,reject) =>{
var img = new Image();
img.src = url;
img.onload = resolve(img);
img.onerror = reject(new Error(url+'此地址失效')); //onerror方法使用img标签
});
return promise;
}
</script>
</html>
此时我故意改错地址
由这个例子可以看出,resolve 和 reject 调用时如果带有参数,那么这些参数会被传递给回调函数。一般情况下,reject 函数的参数通常是 Error 对象的实例,表示抛出错误;resolve 函数的参数除了正常的值外,还可能是另外一个 Promise 对象,如下所示
var p1 = new Promise((resolve,reject) =>{
// todo
});
var p2 = new Promise((resolve,reject) =>{
// todo
resolve(p1);
});
p2 的 resolve 方法将 p1 作为参数,即一个异步操作的结果是返回另一个异步操作
此时 p1 的状态就会传给 p2。也就是说,p1 的状态决定了 p2 的状态。如果 p1 的状态是 Pending,那么 p2 就会等待 p1 的状态改变,如果 p1 已经是 Resolved 或者 Rejected,那么 p2 的回调函数将会立即执行
var p1 = new Promise((resolve,reject) =>{
// todo
});
var p2 = new Promise((resolve,reject) =>{
// todo
resolve(p1);
});
p1.then(()=>{
console.log('p1立即执行')
})
p2.then(()=>{
console.log('p2立即执行')
})
此时都是Pending,控制台无内容,因为p1没有resolve改变状态,如果在p1加上resolve改变状态,p2也会立即执行
还有一点要注意就是调用 resolve 和 rejecte 并不会终止 Promise 函数执行
new Promise((resolve,reject)=>{
resolve(1);
console.log(2);
}).then(val => console.log(val)); // 2 1
一般情况下会return resolve
new Promise((resolve,reject)=>{
return resolve(1);
console.log(2);
}).then(val => console.log(val)); // 1
Promise.prototype.then()
Promise.then ()返回一个新的Promise实例,因此我们可以使用链式写法
new Promise((resolve,reject)=>{
return resolve();
}).then(console.log(2)).then(console.log(1)); //2 1
链式写法的好处就是我们只操作一个对象,也算优化之一,如果后面的想调用前面的value需要return value
Promise.prototype.catch()
Promise.prototype.catch 方法是 .then(null,rejection) 的别名,用于指定发生错误时的回调函数
p.then(val => console.log('fulfilled:',val))
.catch(err => console.log('rejected:',err));
// 等同于
p.then(val => console.log('fulfilled:',val))
.then(null,err => console.log('rejected:',err));
建议使用第一种
var promise = new Promise((resolve,reject)=>{
throw new Error('test');
});
promise.catch(err => console.log(err));
// Error:test
// 等同于下面两种写法
var promise = new Promise((resolve,reject)=>{
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(err => console.log(err));
// 或
var promise = new Promise((resolve,reject) =>{
reject(new Error('test'));
});
promise.catch(err => console.log(err));
抛出错误等同于reject方法,但是如果 Promise 的状态已经变成 Resolved,再抛出错误时无效的
var promise = new Promise((resolve,reject) =>{
resolve('ok');
throw new Error('test');
});
promise.then( val => console.log(val))
.catch( err => console.log(err)); //OK
Promise 在 resolve 语句后面在抛出错误,并不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就会保持下去。 catch返回的也是Promise对象,所以catch后面可以跟上then
new Promise((resolve,reject)=>{
resolve(x+2) //此处报错 因为x未声明
}).catch(err=>{console.log(err)}).then(console.log('go on'))
Promise.resolve()
有时候需要将现有对象转换为 Promise 对象,这个时候就需要用到 Promise.resolve 方法
Promise.resolve('foo')
等价于new Promise(resolve => resolve('foo'))
Promise.resolve 方法的参数分为以下四种情况,虽然有些刻意,了解一下即可(因为看我文章的很多都是萌新)
1.参数是一个 Promise 实例
如果参数是 Promsie 实例,则不做任何修改,直接返回
2.参数是一个 thenable 对象
thenable 对象指的是具有 then 方法的对象,这个比较刻意,属于刻意写此方法名的
var thenable={
then:function(resolve,reject){
resolve('666')
}
}
此方法类似我们上面写的p1 p2,通过p2改变p1的状态
var thenable={
then:function(resolve,reject){
resolve('666')
}
}
var p1=Promise.resolve(thenable)
p1.then(val=>console.log(val)) //666
thenable 对象的 then 方法执行后,对象 p1 的状态就变成 resolved,从而立即执行最后的 then 方法指定的回调函数,输出 666
3.参数不是具有 then 方法的对象或者不是对象
如果对象是一个基本类型的值,或者是一个不具有 then 方法的对象,那么 Promise.resolve 方法返回一个新的 Promise 对象,状态为 Resolved
var promise = Promise.resolve('Hello');
promise.then(val => console.log(val)); //Hello
上面的代码生成一个新的 Promsie 对象的实例 promise,由于字符串 Hello 不属于异步操作(因为字符串对象不具有 then 方法),返回 Promise 实例的状态从生成起就是 Resolved,所以回调函数会立即执行。Promise.resolve 方法的参数会同时传给回调函数
- 不带任何参数
Promise.resolve 方法允许在调用时不带任何参数,直接返回一个 Resolved 状态的 Promise 对象,这个其实用的比较多
var p = Promise.resolve();
p.then(()=>{
// todo
})
Promise.reject()
Promise.reject 方法也会返回一个新的 Promise 实例,状态为 Rejected。这个相对就用得少了
var promise = Promise.reject('出错了');
// 等同于
var promise = new Promise((resolve,reject) => reject('出错了'));
Promise.all()
Promise.all 方法用于将多个 Promise 实例包装成一个新的 Promise 实例
Promise.all 接收一个数组作为参数,如果该参数不是数组,就会调用 Promise.resolve 方法将参数转为 Promise 实例,再进一步处理
简单写个demo,类似之前的图片插入,了解一下即可用的比较少(我反正没用过)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div class="main"></div>
<div id="div"></div>
</body>
<script>
function AddImageAsync(url){
let promise = new Promise((resolve,reject) =>{
var img = new Image();
img.src = url;
img.onload = resolve(img);
img.onerror = reject(new Error(url+'此地址失效')); //onerror方法使用img标签
});
return promise;
}
let img1 = AddImageAsync('http://p4.so.qhmsg.com/bdr/_240_/t012ac97fb54669e6e9.jpg');
let img2 = AddImageAsync('http://p2.so.qhimgs1.com/bdr/_240_/t015225f027a356efee.jpg');
let img3 = AddImageAsync('http://p2.so.qhimgs1.com/bdr/_240_/t010db6b2a0d0582922.jpg');
let imgPromise = Promise.all([img1,img2,img3]);
imgPromise.then((imgArr) => {
console.log(imgArr)
imgArr.forEach(ele=>{
div.append(ele)
})
}).catch( err => console.log(err) );
</script>
</html>
网友评论