[Javascript] Promise
Promise 代表着一个异步操作,这个异步操作现在尚未完成,但在将来某刻会被完成。
Promise 有三种状态
- pending : 初始的状态,尚未知道结果
 - fulfilled : 代表操作成功
 - rejected : 代表操作失败
 
如果 Promise 操作 fulfilled 或者 rejected ,并且对应的处理函数被声明了,则该处理函数被调用。
Promise vs 事件监听器(event listener)
事件监听器善于处理同一对象上重复发生的事情,例如按键、点击鼠标等。对于这些事件,你只关心添加回调函数之后的发生的事情。当处理结果导向的异步事件时,你的代码可能是
img1.callThisIfLoadedOrWhenLoaded(function() {
  // loaded
}).orIfFailedCallThis(function() {
  // failed
});
// and…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
  // all loaded
}).orIfSomeFailedCallThis(function() {
  // one or more failed
});
要实现同样的功能,Promise 的代码更加简洁,更加直观。
img1.ready().then(function() {
  // loaded
}, function() {
  // failed
});
// and…
Promise.all([img1.ready(), img2.ready()]).then(function() {
  // all loaded
}, function() {
  // one or more failed
});
Promise 和 事件监听器很相似,除了以下几点
1. Promise 只经历一次执行,成功或者失败。Promise 不会被执行第二次,也不可以转变执行结果,即执行无法从成功变成失败,反之亦然。而事件监听器可以被反复执行。
2. 如果 Promise 已经执行完毕,成功或者失败,然后再给 Promise 添加对应的回调函数,那么这个对应的回调函数仍然会被执行。也就是说,Promise 添加回调函数后,回调函数先检查在自己被添加之前事件是否已发生,如果发生则执行,如果没有发生就继续等待。而事件监听器只对自己被添加后的事件负责,忽略在自己被添加前的事件。
上面两点非常有利于处理结果导向的异步事件。因为你只需要关系事件发生的结果,成功或者失败,而不需了解事件发生的准确时间。
Promise 的使用
Promise 早已存在一些库里面,例如
创建 Promise
类库中的 Promise 实现大多都遵循标准行为,Promises/A+。 但是 API 会有些不一样。 Javascript 自带的 Promise 采用类似 RSVP.js 的 API 。
var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…
  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});
Promise 构造函数接受一个回调函数作为参数。在创建 Promise 对象时候,执行该回调函数。根据回调函数的结果,成功或者失败,把执行控制权交给 reolve() 或者 reject() 函数。不过 reolve() 或 reject() 真正被执行的时间是在他们通过 Promise.then() 被添加之后。
使用 Promise
promise.then(function(result) {
  console.log(result); // "Stuff worked!"
}, function(err) {
  console.log(err); // Error: "It broke"
});
Promise.then() 接受两个参数,一个用于处理 Promise 执行成功的情况,另一个用于处理 Promise 失败的情况。两个参数都是可选的,所以可以只传函数给第一个参数,用于处理 Promise 成功的情况,也可以只传函数给第二个参数,用于处理 Promise 失败的情况。
链式调用(chaining)
Promise.prototype.then() 和 Promise.prototype.catch() 返回的是一个 Promise ,所以可以形成链式调用。链式调用可用于值传递,或者依次地进行后续的异步操作。
值传递
var promise = new Promise(function(resolve, reject) {
  resolve(1);
});
promise.then(function(val) {
  console.log(val); //
  return val + 2;
}).then(function(val) {
  console.log(val); //
});
当 then() 只是简单地调用另一个函数时,可以只传另一个函数的函数名。例如
get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
});
可以简化为
get('story.json').then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
});
实战代码如下
function getJSON(url) {
  return get(url).then(JSON.parse);
}
串联多个异步操作
如果 then 返回一个值,下一个 then 就会接受这个值继续执行。如果 then 返回另一个 Promise 的对象,那么下一个 then 就等待,直到返回的 Promise 执行完(成功或者失败)。
getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  console.log("Got chapter 1!", chapter1);
});
发送一个 story.json 的请求,获得的结果是一个 story 对象,包含着多个 chapter 的 URL。根据第一个 chapter URL ,再次请求 JSON 数据。执行成功后,最后运行 console.log 。
例子
已知一个目标资源 story.json,需要实现下面几步
1. 向 story.json 发送请求,获得各个章节的 URL,
2. 向各个章节的 URL 发送请求,分别获得各章节内容
3. 将章节内容添加到 HTML 页面中
同步请求数据
由于是同步请求,所有请求都依次阻塞进行。在请求过程中页面也会被阻塞,无进行其他操作。这种方式可以实现数据加载,但是用户体验差。
try {
  var story = getJSONSync('story.json');
  addHtmlToPage(story.heading);
  story.chapterUrls.forEach(function(chapterUrl) {
    var chapter = getJSONSync(chapterUrl);
    addHtmlToPage(chapter.html);
  });
  addTextToPage("All done");
}
catch (err) {
  addTextToPage("Argh, broken: " + err.message);
}
document.querySelector('.spinner').style.display = 'none';
Promise 异步请求数据
getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);
  // TODO: for each url in story.chapterUrls, fetch & display
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
});
上面是一个框架代码,加载每一章节的功能尚未实现。由于数组的 forEach 函数不是异步操作,所以无法简单地使用 forEach 对每一章节进行异步请求。可以将数组转换为 Promise 队列来实现遍历效果。
// Start off with a promise that always resolves
var sequence = Promise.resolve(); // Loop through our chapter urls
story.chapterUrls.forEach(function(chapterUrl) {
// Add these actions to the end of the sequence
sequence = sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
});
或者用 array.reduce 来实现
// Loop through our chapter urls
story.chapterUrls.reduce(function(sequence, chapterUrl) {
// Add these actions to the end of the sequence
return sequence.then(function() {
return getJSON(chapterUrl);
}).then(function(chapter) {
addHtmlToPage(chapter.html);
});
}, Promise.resolve());
将框架代码和加载章节的代码组合起来
getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);
  return story.chapterUrls.reduce(function(sequence, chapterUrl) {
    // Once the last chapter's promise is done…
    return sequence.then(function() {
      // …fetch the next chapter
      return getJSON(chapterUrl);
    }).then(function(chapter) {
      // and add it to the page
      addHtmlToPage(chapter.html);
    });
  }, Promise.resolve());
}).then(function() {
  // And we're all done!
  addTextToPage("All done");
}).catch(function(err) {
  // Catch any error that happened along the way
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  // Always hide the spinner
  document.querySelector('.spinner').style.display = 'none';
});
实现的效果如下:

现在,能执行异步无阻塞地依次加载数据,并且先加载先显示。不过,这种加载方式还有优化空间。
Promise 优化版1
并行加载数据,全部加载后同时显示。效果如下

Promise 优化版2
并行加载数据,根据章节顺序,依次显示各个加载完成的章节。这种加载方式的效果最好。使用 Chrome 浏览器时候,感觉数据加载很快,猜想就是用这种方式进行数据加载的。
效果如下

最后两个优化版的思路和代码参考 JavaScript Promise, Jake Archibald
参考资料
JavaScript Promise, Jake Archibald
[Javascript] Promise的更多相关文章
- Javascript Promise 学习笔记
		
1. 定义:Promise是抽象异步处理对象以及对其进行各种操作的组件,它把异步处理对象和异步处理规则采用统一的接口进行规范化. 2. ES6 Promises 标准中定义的API: ...
 - 【译】JavaScript Promise API
		
原文地址:JavaScript Promise API 在 JavaScript 中,同步的代码更容易书写和 debug,但是有时候出于性能考虑,我们会写一些异步的代码(代替同步代码).思考这样一个场 ...
 - JavaScript Promise:去而复返
		
原文:http://www.html5rocks.com/en/tutorials/es6/promises/ 作者:Jake Archibald 翻译:Amio 女士们先生们,请准备好迎接 Web ...
 - javaScript  Promise 入门
		
Promise是JavaScript的异步编程模式,为繁重的异步回调带来了福音. 一直以来,JavaScript处理异步都是以callback的方式,假设需要进行一个异步队列,执行起来如下: anim ...
 - JavaScript Promise异步实现章节的下载显示
		
Links: JavaScript Promise:简介 1.一章一章顺序地下载显示下载显示 使用Array.reduce()和Promise.resolve()将各章的下载及显示作为整体串联起来. ...
 - Javascript - Promise学习笔记
		
最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...
 - Javascript Promise入门
		
是什么? https://www.promisejs.org/ What is a promise? The core idea behind promises is that a promise r ...
 - JavaScript Promise API
		
同步编程通常来说易于调试和维护,然而,异步编程通常能获得更好的性能和更大的灵活性.异步的最大特点是无需等待."Promises"渐渐成为JavaScript里最重要的一部分,大量的 ...
 - 【JavaScript】JavaScript Promise 探微
		
http://www.html-js.com/article/Promise-translation-JavaScript-Promise-devil-details 原文链接:JavaScript ...
 
随机推荐
- Android开发常见错误类型一览表
			
这是我的第一个博客,我会一直添加我在Android开发中遇到的错误,用来记录我开发中踩过的那些坑 ------------------------分割线------------------------ ...
 - Android开发 --微信支付开发(转载!)(开发工具:Eclipse)
			
Android_APP 微信支付接口开发 日期:2015-10-06 12:47:33 作者: 来源: 人气:3549 1.首先说一下我们在开发微信支付接口的时候遇到最多和最疑惑的问题,那就是明明 a ...
 - 详细查看数据库SQL执行计划
			
DBCC DROPCLEANBUFFERS 清除数据缓存DBCC FREEPROCCACHE 清除执行计划缓存 SET SHOWPLAN_XML ON 此语句导致 SQL Server 不执行 Tr ...
 - 添加Appicon的方法
			
1.将设计好的图片,拖拽到Groups&Files的Resources目录下: 2.修改Resources目录下的“工程名-info.plist”文件 3.修改该文件的Iconfile属性,填 ...
 - Spring中@Autowired注解与自动装配
			
1 使用配置文件的方法来完成自动装配我们编写spring 框架的代码时候.一直遵循是这样一个规则:所有在spring中注入的bean 都建议定义成私有的域变量.并且要配套写上 get 和 set方法. ...
 - javascript——闭包
			
<script type="text/javascript"> //什么是闭包: //是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域 ...
 - Mac使用rz、sz远程上传下载文件
			
习惯了 在windows下的securecrt和xshell的rz 和sz. rz 可以很方便的从客户端传文件到服务器,sz也可以很方便的从服务器传文件到客户端,就算中间隔着跳板机也不影响.在mac下 ...
 - 基于jQuery查找dom的多种方式性能问题
			
这个问题的产生由于我们前端组每个人的编码习惯的差异,最主要的还是因为代码的维护性问题.在此基础上,我对jQuery源码(1.11.3)查找dom节点相关的内容进行了仔细的查阅,虽然并不能理解的很深入 ...
 - 假金币问题-PKUacm1029-ACM
			
假金币 “Gold Bar”银行收到可靠消息:在前次的N 个金币中有一枚重量不同的假金币(其他金币的重量都相同).经济危机之后他们只有一台天平可用.用这台天平,可以称量出左边托盘中的物体是轻于.重于或 ...
 - Ajax实现的长轮询不阻塞同一时间内页面的其他Ajax请求(同域请求)
			
最近要做一个来电的弹屏功能,利用OM 系统的接口,OM系统发送请求到接口程序,分析数据添加到mysql数据库中,然后把最新的数据id 跟今日来电的总的数量存储到memcache 中.弹屏程序根据读取的 ...