请稍候,加载中....

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。

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: 当你需要在当前异步结果的基础上调用一个新的异步任务的时候。

 


Python学习手册-