一、JavaScript Promise
1、简介
Promise是一个ES6提供的类,目的是更加优雅地书写复杂的异步任务。
由于Promise是ES6新增的,所以一些旧的浏览器并不支持,苹果的Safari 10和Windows的Edge 14版本以上的浏览器才支持,这个需要注意。
2、构造Promise
新构建第一个Promise对象:
new Promise(function(resolve, reject) {
// 处理的逻辑
});
通过新建一个Promise对象好像并没有看出它怎么实现“更加优雅地书写复杂的异步任务”。接下来,我们通过需要多次调用异步函数来看下:
普通方式:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Javascript基础学习</title>
</head>
<body>
<h2>JavaScript Promise</h2>
<button onclick="yibu()">异步</button>
<p id="one"></p>
<p id="two"></p>
<p id="three"></p>
</body>
<script>
function yibu() {
setTimeout(function() {
document.getElementById("one").innerHTML = "3000ms -- First";
console.log("First");
setTimeout(function() {
document.getElementById("two").innerHTML = "4000ms -- Second";
console.log("Second");
setTimeout(function() {
document.getElementById("three").innerHTML = "1000ms -- Third";
console.log("Third");
}, 3000);
}, 4000);
}, 1000);
}
</script>
</html>
输出结果:
从上面也能看出,这种“函数瀑布”实现的无论是维护还是异常处理都是特别繁琐的事情,而且也会让缩进格式变得冗赘。
下面我们用Promise来实现同样的功能:
代码:
<script>
function yibu() {
new Promise(function(resolve, reject) {
setTimeout(function() {
document.getElementById("one").innerHTML = "3000ms -- First";
console.log("First");
resolve();
}, 3000);
}).then(function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
document.getElementById("two").innerHTML = "4000ms -- Second";
console.log("Second");
resolve();
}, 4000);
});
}).then(function() {
setTimeout(function() {
document.getElementById("three").innerHTML = "1000ms -- Third";
console.log("Third");
}, 1000);
});
}
</script>
输出结果:
这段代码也是很长的,现在我们不需要完全理解它,代码引起我们注意的是Promise将嵌套格式的代码变成了顺序格式的代码。
3、Promise 的构造函数
Promise构造函数是JavaScript中用于创建Promise对象的内置构造函数。
Promise构造函数接受一个函数作为参数,该函数是同步的并且立即执行,所以我们称之为起始函数。起始函数包含两个参数 resolve和reject,分别表示Promise成功和失败的状态。
起始函数执行成功时,它应该调用resolve函数并传递成功的结果。当起始函数执行失败时,它应该调用reject函数并传递失败的原因。
Promise构造函数返回一个Promise对象,该对象具有以下几个方法:
- then:用于处理Promise成功状态的回调函数
- catch:用于处理Promise失败状态的回调函数
- finally:无论Promise是成功还是失败,都会执行的回到函数。
下面实例是使用Promise构造含税创建Promise对象(当Promise被构造时,起始函数会被同步执行):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Javascript基础学习</title>
</head>
<body>
<h2>JavaScript Promise</h2>
<button onclick="yibu()">异步</button>
<p id="one"></p>
<p id="two"></p>
<p id="three"></p>
</body>
<script>
function yibu() {
const prpmise01 = new Promise(function(resolve, reject) {
setTimeout(function() {
document.getElementById("one").innerHTML = "3000ms -- First";
console.log("First");
resolve("success First");
}, 3000);
})
prpmise01.then(function(result) {
document.getElementById("two").innerHTML = result;
})
const prpmise02 = new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("prpmise02");
reject("error three First");
}, 3000);
})
prpmise02.catch(function(error) {
document.getElementById("three").innerHTML = error;
})
}
</script>
</html>
输入结果:
在上面例子中,我们使用Promise构造函数创建了两个Promise对象,并使用setTimeout模拟一个异步场景。第一个异步操作成功,则会调用resolve函数并传递成功的结果;第二个异步场景失败,则调用reject函数并传递失败的原因。然后,我们使用then方法处理Promise成功状态的回调函数,使用catch方法处理Promise失败状态的回调函数。
接下来我们再看个小示例:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Javascript基础学习</title>
</head>
<body>
<h2>JavaScript Promise</h2>
<button onclick="yibu()">计算(a / b 或 a / c)</button>
<p id="one"></p>
<p id="two"></p>
</body>
<script>
function yibu() {
new Promise(function(resolve, reject) {
var a = 4;
var b = 2;
if (b == 0) {
reject("a / b The denominator cannot be zero")
} else {
var value = a / b;
resolve(value);
}
}).then(function(result) {
document.getElementById("one").innerHTML = "a / b = " + result;
}).catch(function(error) {
document.getElementById("two").innerHTML = "a / c error = " + error;
})
new Promise(function(resolve, reject) {
var a = 4;
var c = 0;
if (c == 0) {
reject("a / c - The denominator cannot be zero")
} else {
var value = a / c;
resolve(value);
}
}).then(function(result) {
document.getElementById("one").innerHTML = "a / c = " + result;
}).catch(function(error) {
document.getElementById("two").innerHTML = "a / c error = " + error;
}).finally(function(){
document.getElementById("three").innerHTML = "End";
})
}
</script>
</html>
输出结果:
说明:Promise类有.then()、.catch()和.finally()三个方法,这三个方法的参数都是一个函数,.then()可以将参数中的函数添加到当前Promise的正常执行序列,.catch()则是设定的异常处理序列,.finally()是在Promise执行的最后一定会执行的序列。.then()传入的函数会按照顺序依次执行,在有任何异常都会直接跳转到catch序列。
例如:
resolve()中可以放置一个参数,用于向下一个then传递一个值,then中的函数也可以返回一个值传递给then。但是,如果 then 中返回的是一个 Promise 对象,那么下一个 then 将相当于对这个返回的 Promise 进行操作,这一点从刚才的计时器的例子中可以看出来。
reject() 参数中一般会传递一个异常给之后的 catch 函数用于处理异常。
注意:
- resolve和reject的作用域只有起始函数,不包括then以及其他序列。
- resolve和reject并不能够使起始函数停止运行,别忘了return。
4、Promise 函数
上述使用计时器实现时,代码还是很长的,所以我们可以将核心代码写成一个Promise函数:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Javascript基础学习</title>
</head>
<body>
<h2>JavaScript Promise</h2>
<button onclick="yibu()">Promise函数</button>
<p id="one"></p>
<p id="two"></p>
<p id="three"></p>
</body>
<script>
// 实现一个Promise函数
function myPromise(delay, msg, pID) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log(msg);
document.getElementById(pID).innerHTML = msg;
resolve();
}, delay);
});
}
function yibu() {
myPromise(4000, "4000ms First", "one").then(function() {
return myPromise(2000, "200ms Second", "two");
}).then(function() {
myPromise(2000, "end", "three");
});
}
</script>
</html>
输出结果:
这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。
5、常见的问题(FAQ)
Q: then、catch 和 finally 序列能否顺序颠倒?
A: 可以,效果完全一样。但不建议这样做,最好按 then-catch-finally 的顺序编写程序。
Q: 除了 then 块以外,其它两种块能否多次使用?
A: 可以,finally 与 then 一样会按顺序执行,但是 catch 块只会执行第一个,除非 catch 块里有异常。所以最好只安排一个 catch 和 finally 块。
Q: then 块如何中断?
A: then 块默认会向下顺序执行,return 是不能中断的,可以通过 throw 来跳转至 catch 实现中断。
Q: 什么时候适合用 Promise 而不是传统回调函数?
A: 当需要多次顺序执行异步操作的时候,例如,如果想通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下就很适合 Promise。
Q: Promise 是一种将异步转换为同步的方法吗?
A: 完全不是。Promise 只不过是一种更良好的编程风格。
Q: 什么时候我们需要再写一个 then 而不是在当前的 then 接着编程?
A: 当你又需要调用一个异步任务的时候。