Promise
是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务
。
由于Promise
是 ES6 新增加的,所以一些旧的浏览器并不支持,苹果的 Safari 10 和 Windows 的 Edge 14 版本以上浏览器才开始支持 ES6 特性。
以下是 Promise 浏览器支持的情况:
Chrome 58 | Edge 14 | Firefox 54 | Safari 10 | Opera 55 |
函数瀑布问题
传统的ajax过程无法可控,当有多个异步请求希望按照一定序列的执行,这可以说是一场噩梦
函数瀑布问题示例
示例当中有三个异步任务步骤,每一个步骤必须等待上一个步骤完成后才能完成
<script>
setTimeout(function () {
document.write("First Step Finished<br>");
setTimeout(function () {
document.write("Second Step Finished<br>");
setTimeout(function () {
document.write("Third Step Finished<br>");
}, 3000);
}, 2000);
}, 1000);
</script>
在上例的解决方案中使用setTimeout来确保上一步完成,但是事实上该方案不能令人满意
- 每一步执行时间事实上无法预知需要多久完成
- 每一步执行状态无法确切保证完成
- 代码结构丑陋,难以优化维护
Promise解决方案
创建Promise 对象:
new Promise(function (resolve, reject) {
// 要做的事情...
});
Promise构造函数
Promise
构造函数只有一个参数,是一个函数,这个函数在构造之后会直接被异步运行,所以我们称之为起始函数。
起始函数包含两个参数resolve
和 reject
。
resolve
和 reject
都是函数.
其中调用 resolve
为正常时执行的回调函数,reject
为出现异常时的回调函数
Promise对象方法
通过构造函数创建Promise对象,然后通过Promise提供的方法来完成异步的顺序调用
Promise 类有 .then()
.catch()
和 .finally()
三个方法,这三个方法的参数都是一个函数。
.then()
可以将参数中的函数添加到当前 Promise 的正常执行序列
.catch()
则是设定 Promise 的异常处理序列,
.finally()
是在 Promise 执行的最后一定会执行的序列。
.then()
传入的函数会按顺序依次执行,有任何异常都会直接跳到catch
序列
Promise实例 1
<script>
new Promise(function (resolve, reject) {
document.write("Run");
});
</script>
执行这段代码会发现document.write("Run");
立即执行了,这部分其实就是异步任务部分,比如一个ajax请求。
Promise...then....
Promise实例 2
现在在上例中加入成功回调, 要进行成功回调,需要加入then
<script>
function success(data){
document.write("<br>" +"*".repeat(10)+data+ "*".repeat(10) + "<br>")
}
new Promise(function (resolve, reject) {
document.write("Run");
resolve("success")
}).then(function(result){
success(result)
});
</script>
在这个例子中,我们在异步任务部分加入了resolve("success")
,resolve
把结果返回给then
部分,传递给success
函数
Promise...catch...
Promise实例 3
在这个例子中,我们将对异步的出现的异常进行处理,进行异常处理需要用到catch
部分
<script>
function success(data){
document.write("<br>" +"*".repeat(10)+data+ "*".repeat(10) + "<br>")
}
function error(data){
document.write("<br><span style=\"color:red\">" +"*".repeat(10)+data+ "*".repeat(10) + "</span><br>")
}
new Promise(function (resolve, reject) {
document.write("Run");
reject("error")
}).then(function(result){
success(result)
}).catch(function(e){
error(e)
});
</script>
如果异步部分执行失败,调用reject
函数,reject结果返回到catch
部分再传递给error
函数执行
完整的Promise
在这个例子中,以一个随机数rand
代表执行结果,如果值为1表示执行成功,如果值为0表示执行失败
<script>
function success(data){
document.write("<br><span style=\"color:green\">" +"*".repeat(10)+data+ "*".repeat(10) + "</span><br>")
}
function error(data){
document.write("<br><span style=\"color:red\">" +"*".repeat(10)+data+ "*".repeat(10) + "</span><br>")
}
new Promise(function (resolve, reject) {
setTimeout(function () {
document.write("执行异步");
rand = Math.round(Math.random())
if (rand == 0){
reject(rand)
} else {
resolve(rand)
}
}, 1000);
}).then(success).catch(error);
</script>
每次执行时,根据rand
随机的显示成功还是失败状态
Promise...finally
finally
保证最后会执行
<script>
new Promise(function (resolve, reject) {
rand = Math.round(Math.random())
if (rand == 0) reject("error");
else resolve("success");
}).then(function (value) {
document.write("<li style=\"color:green\">"+value+"</li>")
}).catch(function (error) {
document.write("<li style=\"color:red\">"+error+"</li>")
}).finally(function () {
document.write("<br>"+"*".repeat(100)+"<br>")
});
</script>
无论执行成功与否,finally
都会执行
解决函数瀑布问题
通过then
的顺序执行可以解决瀑布流问题, then
方法是顺序执行的
<script>
// 虚拟的异步任务
function work_done(step){
return new Promise(function(resolve, reject){
sleep = Math.round(Math.random()*(5-2))*1000
setTimeout(function(){
document.write("<br>" + "*".repeat(20))
document.write("step="+step+" sleep:"+sleep)
document.write("*".repeat(20) + "<br>")
resolve(step+1)
}, sleep)
})
}
new Promise(function (resolve, reject) {
resolve(work_done(1));
}).then(function (value) {
return work_done(value);
}).then(function (value) {
return work_done(value);
throw "An error";
}).catch(function (error) {
document.write("<br><span style=\"color:red\">"+error+"</span>")
});
</script>
执行结果
********************step=1 sleep:2000********************
********************step=2 sleep:3000********************
********************step=3 sleep:2000********************
代码讲解
1. resolve()
中可以放置一个参数用于向下一个 then
传递一个值,
resolve(work_done(1));
这里会返回work_done
的执行结果。这里返回的是一个Promise
对象
2. then
中的函数也可以返回一个值传递给下一个then
。
如果then
中返回的是一个Promise
对象,那么下一个 then
将相当于对这个返回的 Promise
进行操作。
所以在then部分的function中是:
return work_down(value);
3. reject()
参数中一般会传递一个异常给之后的 catch 函数用于处理异常。
但是请注意以下两点:
resolve
和reject
的作用域只有起始函数,不包括 then 以及其他序列;resolve
和reject
并不能够使起始函数停止运行,别忘了 return。
这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。
Promise常见问题
Q: 多个then 块是否多次使用catch、finally?
A: 不需要, catch 块只会执行第一个,除非 catch 块里有异常。
Q: 如何对then块如何中断?
A: then块默认会向下顺序执行,return 会发起下一个then,如果需要中断异步任务流可以通过throw
来跳转至catch
实现中断。
Q: 什么时候适合用 Promise 而不是传统回调函数?
A: 当需要多次顺序执行异步操作的时候,比如多级菜单生成,需要先加载一级菜单顺序,然后根据一级菜单加载二级菜单...。
Q: Promise 是一种将异步转换为同步的方法吗?
A: 完全不是。Promise 只不过是一种更良好的编程风格。
Q: 什么时候我们需要再写一个 then 而不是在当前的 then 接着编程?
A: 当你需要在当前异步结果的基础上调用一个新的异步任务的时候。
讨论区