请稍候,加载中....

JavaScript Promise

Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务

由于Promise 是 ES6 新增加的,所以一些旧的浏览器并不支持,苹果的 Safari 10 和 Windows 的 Edge 14 版本以上浏览器才开始支持 ES6 特性。

以下是 Promise 浏览器支持的情况:

Google Chrome Internet Explorer Firefox Safari Opera
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

resolvereject 都是函数.

其中调用 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 以及其他序列;
  • resolvereject 并不能够使起始函数停止运行,别忘了 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: 当你需要在当前异步结果的基础上调用一个新的异步任务的时候。

 


Python学习手册-