上篇文章介绍了JavaScript异步机制,请看这里

JavaScript异步机制带来的问题

JavaScript异步机制的主要目的是处理非阻塞,在交互的过程中,会需要一些IO操作(比如Ajax请求,文件加载,Node.js中的文件读取等),如果这些操作是同步的,就会阻塞其它操作。

异步机制虽然带来了许多好处,但同时也存在一些不如意的地方。

代码可读性

这样的代码读起来简直累觉不爱啊~~~

  1. operation1(function(err, result) {
  2. operation2(function(err, result) {
  3. operation3(function(err, result) {
  4. operation4(function(err, result) {
  5. operation5(function(err, result) {
  6. // do sth.
  7. })
  8. })
  9. })
  10. })
  11. })

流程控制

异步机制使得流程控制变的有些困难,比如,在N个for循环中的回调函数执行完成之后再做某些事情:

  1. var data = [];
  2. fs.readdir(path, function (err, files) {
  3. if (err) {
  4. console.log(err)
  5. } else {
  6. for (var i in files) {
  7. (function (i) {
  8. fs.stat(path + '/' + files[i], function (err, stats) {
  9. if (err) {
  10. console.log(err)
  11. } else {
  12. o = {
  13. "fileName": files[i].slice(0, -5),
  14. "fileTime": stats.mtime.toString().slice(4, 15)
  15. };
  16. data.push(o);
  17. }
  18. })
  19. })(i);
  20. }
  21. }
  22. });
  23. var html = template('templates/main', data);
  24. res.writeHead(200, {'Content-Type': 'text/html; charset="UTF-8"'});
  25. res.write(html);
  26. res.end();

上面的代码不能获得预期的结果,因为for循环中所有的fs.stat执行结束后,data才会获得预期的值。可是,怎么知道for循环全部执行结束了呢?

异常处理

再看看上面的代码,如果多几个需要处理异常的地方,代码可谓支离破碎了。

Promises

Promise是对异步编程的一种抽象。它是一个代理对象,代表一个必须进行异步处理的函数返回的值或抛出的异常。

Promises全称叫Promises/A+,是一个开放的JavaScript规范,已经被加入ES6中。Promises只是一种规范,从Chrome32起开始支持Promises。

已经实现这个标准的库有Q、when.js、Dojo.deferred、jQuery.deferred等。

鉴于浏览器已经支持Promises,所以尽量使用原生的Promises,对于不支持Promises的浏览器,建议使用这个polyfill: promise-5.0.0.js,Promises其实很简单:

  1. function a (num) {
  2. var promise = new Promise(function (resolve, reject) {
  3. var count = num;
  4. if (count >= 10) {
  5. setTimeout(function () {
  6. count ++;
  7. console.log(count);
  8. resolve(count);
  9. }, 1000);
  10. } else {
  11. reject(count);
  12. }
  13. });
  14. return promise;
  15. }
  16. function b (count) {
  17. var promise = new Promise(function (resolve, reject) {
  18. setTimeout(function () {
  19. count *= 10;
  20. console.log(count);
  21. resolve(count);
  22. }, 1000);
  23. });
  24. return promise;
  25. }
  26. function c () {
  27. console.log('done');
  28. }
  29. function alert(num) {
  30. console.log('您输入的数字为' + num + ',不能小于10!');
  31. }
  32. a(11)
  33. .then(b, alert)
  34. .then(c);

上面这段代码做的事情是:输入num,1s后输出num + 1,2s后输出(num + 1) * 10,最后输出done。

相比a、b两个方法本身,通过Promises定义的方法多了这些东西:

定义一个promise。

  1. var promise = new Promise(function (resolve, reject) {));

完成方法的功能时,调用resolve(data)。

  1. resolve(count);

最后返回promise。

  1. return promise;

首先熟悉一下Promises是怎样定义promise状态的。Promises/A+是这样规定的:

  • 一个promise必须是下面三种状态之一:pending, fulfilled, rejected
  • 当一个promise是pending状态:
    • 可以转变到fulfilled状态或者rejected状态
  • 当一个promise是fulfilled状态:
    • 不可以转变到其他任何状态
    • 必须有一个不能改变的value
  • 当一个promise是rejected状态:
    • 不可以转变到其他任何状态
    • 必须有一个不可改变的reason

上面代码在a()中使用new Promise()实例化了一个promise后,这个promise默认状态是pending。

promise.then()方法有2个参数,分别是onFulfilled、onRejected,这2个参数都是方法名(也就是回调函数),通过这2个参数可以对应promise的fulfilled和rejected2种状态。

通过上面的代码来解释就是:

  • 当a()正常执行结束时,调用resolve(data)将promise的状态改变为fulfilled,并且通过then()的第一个参数onFulfilled将参数data传递给下一个方法b()。

  • 当a()非正常结束,这里认为a()在执行过程中出现了异常时,调用reject(reason)将promise的状态改变为rejected,并且通过then()的第二个参数onRejected将参数reason传递给下一个方法alert()。

在前面说到流程控制的时候提到的for循环的问题还没有解决:如果想等N个for循环中的回调函数执行结束之后做某些事情,该怎么办?

这时候该用到Promise.all()方法了,比如前面提到的一段代码,需求是这样:

在Node.js中,创建http服务器,读取当前目录下articles目录中的所有文件,遍历所有文件,并根据“目录+文件名”读取文件的最后修改时间,最终返回[{文件名,文件修改时间}, {文件名,文件修改时间}, ...]这样一个列表到客户端。

这里存在的问题是,读取目录的操作是异步的,for循环读取文件状态的操作也是异步的,而在for循环中的所有异步操作都执行结束后,需要调用response.writeHead()与response.write()将所有异步数据返回到客户端。在使用when.js之前,我能想到的就是把for循环中的异步操作变为同步操作,最后再返回数据,但是就会阻塞其他的同步操作,显然这违背了异步机制。

利用Promise.all()改造过后的代码:

  1. var http = require('http'),
  2. fs =require('fs'),
  3. connect = require('connect'),
  4. Promise = require('promise');
  5. function readDir (path) {
  6. return new Promise(function (resolve, reject) {
  7. fs.readdir(path, function (err, files) {
  8. if (err) {
  9. reject(err);
  10. } else {
  11. resolve({
  12. "path": path,
  13. "files": files
  14. });
  15. }
  16. });
  17. });
  18. }
  19. function getFileStats (data) {
  20. var
  21. files = data.files,
  22. promiseList = [];
  23. for (var i in files) {
  24. (function (i) {
  25. var promise = new Promise(function (resolve, reject) {
  26. fs.stat(data.path + '/' + files[i], function (err, stats) {
  27. if (err) {
  28. reject(err)
  29. } else {
  30. var o = {
  31. "fileName": files[i].slice(0, -5),
  32. "fileTime": stats.mtime.toString().slice(4, 15)
  33. };
  34. resolve(o);
  35. }
  36. })
  37. });
  38. promiseList.push(promise);
  39. })(i);
  40. }
  41. return Promise.all(promiseList);
  42. }
  43. var app = connect()
  44. .use(function(req, res) {
  45. if (req.url === '/favicon.ico') {
  46. return;
  47. } else {
  48. readDir('articles/fe').then(getFileStats)
  49. .then(function (o) {
  50. res.writeHead(200, {'Content-Type': 'text/html; charset="UTF-8"'});
  51. res.write(JSON.stringify(o));
  52. res.end();
  53. });
  54. }
  55. })
  56. .listen(8080);

浏览器上的结果:

Promise.all(array)需要传入一个promise数组,其中数组中的每一个promise在fulfill时都会执行resolve(data),这里的data就是前面for循环中每一次异步操作中获得的数据。在promise.all()执行过后,会将每次resolve(data)中的data拼成一个数组,通过then()传递给下一个promise。

Promise.race()

Promise.race()为异步任务提供了竞争机制。比如在N个异步任务中,在最快获得结果的任务之后做某些事情,可以使用Promise.race()。

使用Promise.race()同Promise.all()类似,传入的参数都是promise数组,返回promise数组中最早fulfill的promise,或者返回最早reject的promise。

  1. function a () {
  2. var promise = new Promise(function (resolve, reject) {
  3. setTimeout(function () {
  4. resolve('a');
  5. }, 1002);
  6. });
  7. return promise;
  8. }
  9. function b () {
  10. var promise = new Promise(function (resolve, reject) {
  11. setTimeout(function () {
  12. resolve('b');
  13. }, 1001);
  14. });
  15. return promise;
  16. }
  17. function c (data) {
  18. console.log(data + ' first!');
  19. }
  20. Promise.race([a(), b()]).then(c);

执行结果:b first!

JavaScript Promises的更多相关文章

  1. 异步编程之Javascript Promises 规范介绍

    什么是 Promises Promises是一种关于异步编程的规范,目的是将异步处理对象和处理规则进行规范化,为异步编程提供统一接口. 传统的回调函数 说到JavaScript的异步编程处理,通常我们 ...

  2. Javascript Promises 介绍

    什么是 Promises Promises是一种关于异步编程的规范,目的是将异步处理对象和处理规则进行规范化,为异步编程提供统一接口.   传统的回调函数 说到JavaScript的异步编程处理,通常 ...

  3. JavaScript Promises 实现框架一览及对比

    In my previous post in Danish I looked at how to perform asynchronous calls by using promises. Now t ...

  4. javascript promises powered by BlueBird

    什么是promises? 为什么需要promises? 参见: https://promisesaplus.com/ 使用示例: 使用promises之前,代码的编写方式: 使用promises之后: ...

  5. Javascript Promises学习

    Promise对象的三个状态 pending(进行中) fulfilled(已成功) rejected(已失败) Promise代表一个异步操作,对象的状态一旦改变,就不会再改变 Promise构造函 ...

  6. JavaScript进阶之路——认识和使用Promise,重构你的Js代码

    一转眼,这2015年上半年就过去了,差不多一个月没有写博客了,"罪过罪过"啊~~.进入了七月份,也就意味着我们上半年苦逼的单身生活结束了,从此刻起,我们要打起十二分的精神,开始下半 ...

  7. 【JavaScript】JavaScript Promise 探微

    http://www.html-js.com/article/Promise-translation-JavaScript-Promise-devil-details 原文链接:JavaScript ...

  8. JavaScript周报#183

    This week’s JavaScript news Read this issue on the Web | Issue Archive JavaScript Weekly Issue 183Ma ...

  9. JavaScript Promise启示录--(转)

    本博文转至:http://www.csdn.net/article/2014-05-28/2819979-JavaScript-Promise [编者按]JavaScript是一种基于对象和事件驱动并 ...

随机推荐

  1. unity 优秀开源项目

    ihaiu.GUIDRef (查看项目资源使用情况) http://blog.ihaiu.com/unity-GUIDRef Ihaiu.PoolManager (对象池) http://github ...

  2. Windows 10安装uWSGI:不可行、失败了

    Windows 10家庭中文版,Python 3.6.4,uwsgi-2.0.17.tar.gz,压缩工具-7-zip 提示:请不要和我一样尝试,浪费时间,去Linux上玩吧! 几个小时的安装经历 昨 ...

  3. JAVA随笔(二)

    在函数传参时,double传给int是不行的,反过来可以.参数只能传值.当参数是字符串时,传递的只是串值:但对于数组来说,传递的是管理权,也就是指针 对象变量是对象管理者. cast转型:基本类型与对 ...

  4. 洛谷P1782 旅行商的背包

    传送门啦 这个题不用二进制优化的话根本不行,现学的二进制优化,调了一段时间终于A了,不容易.. 如果不懂二进制优化的话可以去看我那个博客 二进制优化多重背包入口 不想TLE,不要打memset,一定要 ...

  5. JS实现逼真的雪花飘落特效

    逼真的雪花飘落特效 效果图: 图片素材 : --> ParticleSmoke.png 代码如下,复制即可使用: <!doctype html> <html> <h ...

  6. 2、图文讲解.NET CLR是什么

    大家首先要清楚的是,.NET平台与C#不是一回事.这点大家一定要明白,对开发人员来讲他有两个概念.第一,它是C#,VB.net等程序运行的平台.第二,它因为为这些语言提供了丰富的类库(称之为基类库), ...

  7. javascript输入验证数字方法,适合充值时输入正整数验证

    说明:用于验证正整数的输入,不允许输入其他字符. html: <input type="text" id="sell_jobNum" name=" ...

  8. ASP.NET:插件化机制

    概述 nopCommerce的插件机制的核心是使用BuildManager.AddReferencedAssembly将使用Assembly.Load加载的插件程序集添加到应用程序域的引用中.具体实现 ...

  9. 【LOJ】#2275. 「JXOI2017」颜色

    题解 我们枚举右端点判断合法的左端点有哪些 首先,记录一下右端点右边的点的pre,也就是这个数字前一个出现的位置,取所有小于枚举右端点r的值中最大的一个做为l,用优先队列维护即可,[l + 1,r]就 ...

  10. 【POJ】1026.Cipher

    题解 置换群的快速幂,然而我姿势水平不高,样例过不去,然后才明白这个置换的意思是这个位置上的数代表要把原位置的某个数换过来 需要新开一个数组存结果 代码 #include <iostream&g ...