所以, 从表面上看, Promise 只是能够简化层层回调的写法, 而实质上, Promise 的精髓是 "状态", 用维护状态, 传递状态的方式来使得回调函数能够及时调用, 它比传递 callback 函数要简单, 灵活的多. 所以使用 Promise 的正确场景是这样的:
- runAsync1()
- .then(function(data){
- console.log(data);
- return runAsync2();
- })
- .then(function(data){
- console.log(data);
- return runAsync3();
- })
- .then(function(data){
- console.log(data);
- });
这样能够按顺序, 每隔两秒输出每个异步回调中的内容, 在 runAsync2 中传给 resolve 的数据, 能在接下来的 then 方法中拿到. 运行结果如下:
猜猜 runAsync1,runAsync2,runAsync3 这三个函数都是如何定义的? 没错, 就是下面这样
- function runAsync1(){
- var p = new Promise(function(resolve, reject){
- // 做一些异步操作
- setTimeout(function(){
- console.log('异步任务 1 执行完成');
- resolve('随便什么数据 1');
- }, 1000);
- });
- return p;
- }
- function runAsync2(){
- var p = new Promise(function(resolve, reject){
- // 做一些异步操作
- setTimeout(function(){
- console.log('异步任务 2 执行完成');
- resolve('随便什么数据 2');
- }, 2000);
- });
- return p;
- }
- function runAsync3(){
- var p = new Promise(function(resolve, reject){
- // 做一些异步操作
- setTimeout(function(){
- console.log('异步任务 3 执行完成');
- resolve('随便什么数据 3');
- }, 2000);
- });
- return p;
- }
在 then 方法中, 你也可以直接 return 数据而不是 Promise 对象, 在后面的 then 中就可以接收到数据了, 比如我们把上面的代码修改成这样:
- runAsync1()
- .then(function(data){
- console.log(data);
- return runAsync2();
- })
- .then(function(data){
- console.log(data);
- return '直接返回数据'; // 这里直接返回数据
- })
- .then(function(data){
- console.log(data);
- });
那么输出就变成了这样:
reject 的用法
到这里, 你应该对 "Promise 是什么玩意" 有了最基本的了解. 那么我们接着来看看 ES6 的 Promise 还有哪些功能. 我们光用了 resolve, 还没用 reject 呢, 它是做什么的呢? 事实上, 我们前面的例子都是只有 "执行成功" 的回调, 还没有 "失败" 的情况, reject 的作用就是把 Promise 的状态置为 rejected, 这样我们在 then 中就能捕捉到, 然后执行 "失败" 情况的回调. 看下面的代码.
按 Ctrl+C
按 Ctrl+C
getNumber 函数用来异步获取一个数字, 2 秒后执行完成, 如果数字小于等于 5, 我们认为是 "成功" 了, 调用 resolve 修改 Promise 的状态. 否则我们认为是 "失败" 了, 调用 reject 并传递一个参数, 作为失败的原因.
运行 getNumber 并且在 then 中传了两个参数, then 方法可以接受两个参数, 第一个对应 resolve 的回调, 第二个对应 reject 的回调. 所以我们能够分别拿到他们传过来的数据. 多次运行这段代码, 你会随机得到下面两种结果:
或者
catch 的用法
我们知道 Promise 对象除了 then 方法, 还有一个 catch 方法, 它是做什么用的呢? 其实它和 then 的第二个参数一样, 用来指定 reject 的回调, 用法是这样:
- getNumber()
- .then(function(data){
- console.log('resolved');
- console.log(data);
- })
- .catch(function(reason){
- console.log('rejected');
- console.log(reason);
- });
效果和写在 then 的第二个参数里面一样. 不过它还有另外一个作用: 在执行 resolve 的回调 (也就是上面 then 中的第一个参数) 时, 如果抛出异常了(代码出错了), 那么并不会报错卡死 JS, 而是会进到这个 catch 方法中. 请看下面的代码:
- getNumber()
- .then(function(data){
- console.log('resolved');
- console.log(data);
- console.log(somedata); // 此处的 somedata 未定义
- })
- .catch(function(reason){
- console.log('rejected');
- console.log(reason);
- });
在 resolve 的回调中, 我们 console.log(somedata); 而 somedata 这个变量是没有被定义的. 如果我们不用 Promise, 代码运行到这里就直接在控制台报错了, 不往下运行了. 但是在这里, 会得到这样的结果:
也就是说进到 catch 方法里面去了, 而且把错误原因传到了 reason 参数中. 即便是有错误的代码也不会报错了, 这与我们的 try/catch 语句有相同的功能.
all 的用法
Promise 的 all 方法提供了并行执行异步操作的能力, 并且在所有异步操作执行完后才执行回调. 我们仍旧使用上面定义好的 runAsync1,runAsync2,runAsync3 这三个函数, 看下面的例子:
- Promise
- .all([runAsync1(), runAsync2(), runAsync3()])
- .then(function(results){
- console.log(results);
- });
用 Promise.all 来执行, all 接收一个数组参数, 里面的值最终都算返回 Promise 对象. 这样, 三个异步操作的并行执行的, 等到它们都执行完后才会进到 then 里面. 那么, 三个异步操作返回的数据哪里去了呢? 都在 then 里面呢, all 会把所有异步操作的结果放进一个数组中传给 then, 就是上面的 results. 所以上面代码的输出结果就是:
有了 all, 你就可以并行执行多个异步操作, 并且在一个回调中处理所有的返回数据, 是不是很酷? 有一个场景是很适合用这个的, 一些游戏类的素材比较多的应用, 打开网页时, 预先加载需要用到的各种资源如图片, flash 以及各种静态文件. 所有的都加载完后, 我们再进行页面的初始化.
race 的用法
all 方法的效果实际上是「谁跑的慢, 以谁为准执行回调」, 那么相对的就有另一个方法「谁跑的快, 以谁为准执行回调」, 这就是 race 方法, 这个词本来就是赛跑的意思. race 的用法与 all 一样, 我们把上面 runAsync1 的延时改为 1 秒来看一下:
- Promise
- .race([runAsync1(), runAsync2(), runAsync3()])
- .then(function(results){
- console.log(results);
- });
这三个异步操作同样是并行执行的. 结果你应该可以猜到, 1 秒后 runAsync1 已经执行完了, 此时 then 里面的就执行了. 结果是这样的:
你猜对了吗? 不完全, 是吧. 在 then 里面的回调开始执行时, runAsync2()和 runAsync3()并没有停止, 仍旧再执行. 于是再过 1 秒后, 输出了他们结束的标志.
这个 race 有什么用呢? 使用场景还是很多的, 比如我们可以用 race 给某个异步请求设置超时时间, 并且在超时后执行相应的操作, 代码如下:
- // 请求某个图片资源
- function requestImg(){
- var p = new Promise(function(resolve, reject){
- var img = new Image();
- img.onload = function(){
- resolve(img);
- }
- img.src = 'xxxxxx';
- });
- return p;
- }
- // 延时函数, 用于给请求计时
- function timeout(){
- var p = new Promise(function(resolve, reject){
- setTimeout(function(){
- reject('图片请求超时');
- }, 5000);
- });
- return p;
- }
- Promise
- .race([requestImg(), timeout()])
- .then(function(results){
- console.log(results);
- })
- .catch(function(reason){
- console.log(reason);
- });
requestImg 函数会异步请求一张图片, 我把地址写为 "xxxxxx", 所以肯定是无法成功请求到的. timeout 函数是一个延时 5 秒的异步操作. 我们把这两个返回 Promise 对象的函数放进 race, 于是他俩就会赛跑, 如果 5 秒之内图片请求成功了, 那么遍进入 then 方法, 执行正常的流程. 如果 5 秒钟图片还未成功返回, 那么 timeout 就跑赢了, 则进入 catch, 报出 "图片请求超时" 的信息. 运行结果如下:
来源: http://www.bubuko.com/infodetail-2976350.html