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 相关文章推荐
javascript对象的property和prototype是这样一种关系
Mar 24 Javascript
js弹出框轻量级插件jquery.boxy使用介绍
Jan 15 Javascript
在JavaScript中typeof的用途介绍
Apr 11 Javascript
Javascript中各种trim的实现详细解析
Dec 10 Javascript
javascript感应鼠标图片透明度显示的方法
Feb 24 Javascript
JavaScript代码判断点击第几个按钮
Dec 13 Javascript
JavaScript中localStorage对象存储方式实例分析
Jan 12 Javascript
AngularJs 最新验证手机号码的实例,成功测试通过
Nov 26 Javascript
详解如何理解vue的key属性
Apr 14 Javascript
react用Redux中央仓库实现一个todolist
Sep 29 Javascript
vue+element导航栏高亮显示的解决方式
Nov 12 Javascript
详解三种方式在React中解决绑定this的作用域问题并传参
Aug 18 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使用Jpgraph绘制简单X-Y坐标图的方法
2015/06/10 PHP
PHP sdk文档处理常用代码示例解析
2020/12/09 PHP
jQuery easyui datagrid动态查询数据实例讲解
2013/02/26 Javascript
jQuery fadeTo方法调整图片的透明度使用介绍
2013/05/06 Javascript
js 实现日期灵活格式化的小例子
2013/07/14 Javascript
关于jQuery中.attr()和.prop()的问题探讨
2013/09/06 Javascript
jquery.ajax之beforeSend方法使用介绍
2014/12/08 Javascript
javascript显示中文日期的方法
2015/06/18 Javascript
纯jquery实现模仿淘宝购物车结算
2015/08/20 Javascript
js中class的点击事件没有效果的解决方法
2016/10/13 Javascript
JavaScript中关于iframe滚动条的去除和保留
2016/11/17 Javascript
node.js基于mongodb的搜索分页示例
2017/01/22 Javascript
JavaScript实现实时更新系统时间的实例代码
2017/04/04 Javascript
pace.js和NProgress.js两个加载进度插件的一点小总结
2018/01/31 Javascript
jQuery常见的遍历DOM操作详解
2018/09/05 jQuery
vue组件内部引入外部js文件的方法
2020/01/18 Javascript
python实现根据用户输入从电影网站获取影片信息的方法
2015/04/07 Python
python分析网页上所有超链接的方法
2015/05/08 Python
python实现数值积分的Simpson方法实例分析
2015/06/05 Python
如何通过50行Python代码获取公众号全部文章
2019/07/12 Python
Python 实现使用空值进行赋值 None
2020/03/12 Python
Keras 实现加载预训练模型并冻结网络的层
2020/06/15 Python
整理的15个非常有用的 HTML5 开发教程和速查手册
2011/10/18 HTML / CSS
Otticanet意大利:最顶尖的世界名牌眼镜, 能得到打折季的价格
2019/03/10 全球购物
应届大学生自荐信格式
2013/09/21 职场文书
土木工程毕业生推荐信
2013/10/28 职场文书
幼儿园三八妇女节活动方案
2014/03/11 职场文书
央视元宵晚会主持串词
2014/03/25 职场文书
大学生就业自荐书
2014/06/16 职场文书
装饰技术负责人岗位职责
2015/04/13 职场文书
餐饮店长岗位职责
2015/04/14 职场文书
道歉短信大全
2015/05/12 职场文书
运动会开幕式通讯稿
2015/07/18 职场文书
初一军训感言
2015/08/01 职场文书
导游词之山西-五老峰
2019/10/07 职场文书
Python+OpenCV实现图片中的圆形检测
2022/04/07 Python