JavaScript之promise_动力节点Java学院整理


Posted in Javascript onJuly 03, 2017

在JavaScript的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:

function callback() {
 console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');

观察上述代码执行,在Chrome的控制台输出可以看到:

before setTimeout()
after setTimeout()
(等待1秒后)
Done

可见,异步操作会在将来的某个时间点触发一个函数调用。
AJAX就是典型的异步操作。以上一节的代码为例:

request.onreadystatechange = function () {
 if (request.readyState === 4) {
  if (request.status === 200) {
   return success(request.responseText);
  } else {
   return fail(request.status);
  }
 }
}

把回调函数success(request.responseText)和fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。

有没有更好的写法?比如写成这样:

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
 .ifFail(fail);

这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。

古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。

Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。先测试一下你的浏览器是否支持Promise:

'use strict';

new Promise(function () {});
// 直接运行测试:
alert('支持Promise!');

我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

function test(resolve, reject) {
 var timeOut = Math.random() * 2;
 log('set timeout to: ' + timeOut + ' seconds.');
 setTimeout(function () {
  if (timeOut < 1) {
   log('call resolve()...');
   resolve('200 OK');
  }
  else {
   log('call reject()...');
   reject('timeout in ' + timeOut + ' seconds.');
  }
 }, timeOut * 1000);
}

这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK'),如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:

var p1 = new Promise(test);
var p2 = p1.then(function (result) {
 console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
 console.log('失败:' + reason);
});

变量p1是一个Promise对象,它负责执行test函数。由于test函数在内部是异步执行的,当test函数执行成功时,我们告诉Promise对象:

// 如果成功,执行这个函数:
p1.then(function (result) {
 console.log('成功:' + result);
});

当test函数执行失败时,我们告诉Promise对象:

p2.catch(function (reason) {
 console.log('失败:' + reason);
});

Promise对象可以串联起来,所以上述代码可以简化为:

new Promise(test).then(function (result) {
 console.log('成功:' + result);
}).catch(function (reason) {
 console.log('失败:' + reason);
});

实际测试一下,看看Promise是如何异步执行的:

'use strict';

// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
 logging.removeChild(logging.children[logging.children.length - 1]);
}

// 输出log到页面:
function log(s) {
 var p = document.createElement('p');
 p.innerHTML = s;
 logging.appendChild(p);
}
new Promise(function (resolve, reject) {
 log('start new Promise...');
 var timeOut = Math.random() * 2;
 log('set timeout to: ' + timeOut + ' seconds.');
 setTimeout(function () {
  if (timeOut < 1) {
   log('call resolve()...');
   resolve('200 OK');
  }
  else {
   log('call reject()...');
   reject('timeout in ' + timeOut + ' seconds.');
  }
 }, timeOut * 1000);
}).then(function (r) {
 log('Done: ' + r);
}).catch(function (reason) {
 log('Failed: ' + reason);
});

Log:

start new Promise...
set timeout to: 0.5354042750614991 seconds.
call resolve()...
Done: 200 OK

可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:

JavaScript之promise_动力节点Java学院整理

Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。
要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:

job1.then(job2).then(job3).catch(handleError);

其中,job1、job2和job3都是Promise对象。
下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:

'use strict';

var logging = document.getElementById('test-promise2-log');
while (logging.children.length > 1) {
 logging.removeChild(logging.children[logging.children.length - 1]);
}

function log(s) {
 var p = document.createElement('p');
 p.innerHTML = s;
 logging.appendChild(p);
}
// 0.5秒后返回input*input的计算结果:
function multiply(input) {
 return new Promise(function (resolve, reject) {
  log('calculating ' + input + ' x ' + input + '...');
  setTimeout(resolve, 500, input * input);
 });
}

// 0.5秒后返回input+input的计算结果:
function add(input) {
 return new Promise(function (resolve, reject) {
  log('calculating ' + input + ' + ' + input + '...');
  setTimeout(resolve, 500, input + input);
 });
}

var p = new Promise(function (resolve, reject) {
 log('start new Promise...');
 resolve(123);
});

p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
 log('Got value: ' + result);
});

Log:

start new Promise...
calculating 123 x 123...
calculating 15129 + 15129...
calculating 30258 x 30258...
calculating 915546564 + 915546564...
Got value: 1831093128

setTimeout可以看成一个模拟网络等异步执行的函数。现在,我们把上一节的AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:

'use strict';

// ajax函数将返回Promise对象:
function ajax(method, url, data) {
 var request = new XMLHttpRequest();
 return new Promise(function (resolve, reject) {
  request.onreadystatechange = function () {
   if (request.readyState === 4) {
    if (request.status === 200) {
     resolve(request.responseText);
    } else {
     reject(request.status);
    }
   }
  };
  request.open(method, url);
  request.send(data);
 });
}
var log = document.getElementById('test-promise-ajax-result');
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容
 log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码
 log.innerText = 'ERROR: ' + status;
});

除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

var p1 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
 console.log(results); // 获得一个Array: ['P1', 'P2']
});

有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

var p1 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
 console.log(result); // 'P1'
});

由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。

如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
脚本吧 - 幻宇工作室用到js,超强推荐share.js
Dec 23 Javascript
js计算页面刷新的次数
Jul 20 Javascript
基于jquery的has()方法以及与find()方法以及filter()方法的区别详解
Apr 26 Javascript
jQuery prev ~ siblings选择器使用介绍
Aug 09 Javascript
jQuery点击输入框显示验证码图片
May 19 Javascript
JQuery DIV 动态隐藏和显示的方法
Jun 23 Javascript
微信小程序 页面跳转传参详解
Oct 28 Javascript
javascript实现获取一个日期段内每天不同的价格(计算入住总价格)
Feb 05 Javascript
解决layui页面按钮点击无反应,也不报错的问题
Sep 29 Javascript
taro小程序添加骨架屏的实现代码
Nov 15 Javascript
vue动态加载SVG文件并修改节点数据的操作代码
Aug 17 Javascript
浅谈Vue static 静态资源路径 和 style问题
Nov 07 Javascript
JavaScript之DOM_动力节点Java学院整理
Jul 03 #Javascript
javaScript中封装的各种写法示例(推荐)
Jul 03 #Javascript
JavaScript之class继承_动力节点Java学院整理
Jul 03 #Javascript
JavaScript之浏览器对象_动力节点Java学院整理
Jul 03 #Javascript
JavaScript之underscore_动力节点Java学院整理
Jul 03 #Javascript
React组件生命周期详解
Jul 03 #Javascript
利用Vue.js实现求职在线之职位查询功能
Jul 03 #Javascript
You might like
php通过数组实现多条件查询实现方法(字符串分割)
2014/05/06 PHP
解析 thinkphp 框架中的部分方法
2017/05/07 PHP
详解PHP中mb_strpos的使用
2018/02/04 PHP
thinkphp5 migrate数据库迁移工具
2018/02/20 PHP
PHP时间日期增减操作示例【date strtotime实现加一天、加一月等操作】
2018/12/21 PHP
Web Inspector:关于在 Sublime Text 中调试Js的介绍
2013/04/18 Javascript
JS动态调用方法名示例介绍
2013/12/18 Javascript
jQuery实现鼠标经过图片预览大图效果
2014/04/10 Javascript
JavaScript代码应该放在HTML代码哪个位置比较好?
2014/10/16 Javascript
浅谈JavaScript实现面向对象中的类
2014/12/09 Javascript
NodeJS学习笔记之Http模块
2015/01/13 NodeJs
js获取图片宽高的方法
2015/11/25 Javascript
NodeJS链接MySql数据库的操作方法
2017/06/27 NodeJs
详解前端路由实现与react-router使用姿势
2017/08/07 Javascript
vue.js实现点击后动态添加class及删除同级class的实现代码
2018/04/04 Javascript
Vue2.0仿饿了么webapp单页面应用详细步骤
2018/07/08 Javascript
Vue 实现列表动态添加和删除的两种方法小结
2018/09/07 Javascript
JS forEach跳出循环2种实现方法
2020/06/24 Javascript
微信小程序抽奖组件的使用步骤
2021/01/11 Javascript
Python中的with...as用法介绍
2015/05/28 Python
基于Python实现文件大小输出
2016/01/11 Python
pandas通过loc生成新的列方法
2018/11/28 Python
python 实现方阵的对角线遍历示例
2019/11/29 Python
Tensorflow与Keras自适应使用显存方式
2020/06/22 Python
英国票务网站:Ticketmaster英国
2018/08/27 全球购物
中专生学习生活的自我评价分享
2013/10/27 职场文书
公司成立感言
2014/01/11 职场文书
关于祖国的演讲稿
2014/05/04 职场文书
王力宏牛津大学演讲稿
2014/05/22 职场文书
电话营销开场白
2015/05/29 职场文书
成功的商业计划书这样写才最靠谱
2019/07/12 职场文书
创业计划书之宠物店
2019/09/19 职场文书
Promise面试题详解之控制并发
2021/05/14 面试题
详解PHP设计模式之依赖注入模式
2021/05/25 PHP
PostgreSQL数据库创建并使用视图以及子查询
2022/04/11 PostgreSQL
python计算列表元素与乘积详情
2022/08/05 Python