前言

Promise的重要性我认为我没有必要多讲,概括起来说就是必须得掌握,而且还要掌握透彻。这篇文章的开头,主要跟大家分析一下,为什么会有Promise出现。

在实际的使用当中,有非常多的应用场景我们不能立即知道应该如何继续往下执行。最重要也是最主要的一个场景就是ajax请求。通俗来说,由于网速的不同,可能你得到返回值的时间也是不同的,这个时候我们就需要等待,结果出来了之后才知道怎么样继续下去。

  1. // 简单的ajax原生实现
  2. var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
  3. var result;
  4.  
  5. var XHR = new XMLHttpRequest();
  6. XHR.open('GET', url, true);
  7. XHR.send();
  8.  
  9. XHR.onreadystatechange = function() {
  10. if (XHR.readyState == 4 && XHR.status == 200) {
  11. result = XHR.response;
  12. console.log(result);
  13. }
  14. }

在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到我们想要的数据,之后我们才能开始处理数据。

这样做看上去并没有什么麻烦,但是如果这个时候,我们还需要做另外一个ajax请求,这个新的ajax请求的其中一个参数,得从上一个ajax请求中获取,这个时候我们就不得不如下这样做:

  1. var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
  2. var result;
  3.  
  4. var XHR = new XMLHttpRequest();
  5. XHR.open('GET', url, true);
  6. XHR.send();
  7.  
  8. XHR.onreadystatechange = function() {
  9. if (XHR.readyState == 4 && XHR.status == 200) {
  10. result = XHR.response;
  11. console.log(result);
  12.  
  13. // 伪代码
  14. var url2 = 'http:xxx.yyy.com/zzz?ddd=' + result.someParams;
  15. var XHR2 = new XMLHttpRequest();
  16. XHR2.open('GET', url, true);
  17. XHR2.send();
  18. XHR2.onreadystatechange = function() {
  19. ...
  20. }
  21. }
  22. }

当出现第三个ajax(甚至更多)仍然依赖上一个请求的时候,我们的代码就变成了一场灾难。这场灾难,往往也被称为回调地狱

因此我们需要一个叫做Promise的东西,来解决这个问题。

当然,除了回调地狱之外,还有一个非常重要的需求:为了我们的代码更加具有可读性和可维护性,我们需要将数据请求与数据处理明确的区分开来。上面的写法,是完全没有区分开,当数据变得复杂时,也许我们自己都无法轻松维护自己的代码了。这也是模块化过程中,必须要掌握的一个重要技能,请一定重视。

从前面几篇文中的知识我们可以知道,当我们想要确保某代码在谁谁之后执行时,我们可以利用函数调用栈,将我们想要执行的代码放入回调函数中。

  1. // 一个简单的封装
  2. function want() {
  3. console.log('这是你想要执行的代码');
  4. }
  5.  
  6. function fn(want) {
  7. console.log('这里表示执行了一大堆各种代码');
  8.  
  9. // 其他代码执行完毕,最后执行回调函数
  10. want && want();
  11. }
  12.  
  13. fn(want);

利用回调函数封装,是我们在初学JavaScript时常常会使用的技能。

确保我们想要的代码压后执行,除了利用函数调用栈的执行顺序之外,我们还可以利用上一篇文章所述的队列机制。

  1. function want() {
  2. console.log('这是你想要执行的代码');
  3. }
  4.  
  5. function fn(want) {
  6. // 将想要执行的代码放入队列中,根据事件循环的机制,我们就不用非得将它放到最后面了,由你自由选择
  7. want && setTimeout(want, 0);
  8. console.log('这里表示执行了一大堆各种代码');
  9. }
  10.  
  11. fn(want);

如果浏览器已经支持了原生的Promise对象,那么我们就知道,浏览器的js引擎里已经有了Promise队列,这样就可以利用Promise将任务放在它的队列中去。

  1. function want() {
  2. console.log('这是你想要执行的代码');
  3. }
  4.  
  5. function fn(want) {
  6. console.log('这里表示执行了一大堆各种代码');
  7.  
  8. // 返回Promise对象
  9. return new Promise(function(resolve, reject) {
  10. if (typeof want == 'function') {
  11. resolve(want);
  12. } else {
  13. reject('TypeError: '+ want +'不是一个函数')
  14. }
  15. })
  16. }
  17.  
  18. fn(want).then(function(want) {
  19. want();
  20. })
  21.  
  22. fn('1234').catch(function(err) {
  23. console.log(err);
  24. })

看上去变得更加复杂了。可是代码变得更加健壮,处理了错误输入的情况。

为了更好的往下扩展Promise的应用,这里需要先跟大家介绍一下Promsie的基础知识。

一、 Promise对象有三种状态,他们分别是:

  • pending: 等待中,或者进行中,表示还没有得到结果
  • resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行
  • rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行

这三种状态不受外界影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆。在Promise对象的构造函数中,将一个函数作为第一个参数。而这个函数,就是用来处理Promise的状态变化。

  1. new Promise(function(resolve, reject) {
  2. if(true) { resolve() };
  3. if(false) { reject() };
  4. })

上面的resolve和reject都为一个函数,他们的作用分别是将状态修改为resolved和rejected。

二、 Promise对象中的then方法,可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行。

  1. function fn(num) {
  2. return new Promise(function(resolve, reject) {
  3. if (typeof num == 'number') {
  4. resolve();
  5. } else {
  6. reject();
  7. }
  8. }).then(function() {
  9. console.log('参数是一个number值');
  10. }, function() {
  11. console.log('参数不是一个number值');
  12. })
  13. }
  14.  
  15. fn('hahha');
  16. fn(1234);

then方法的执行结果也会返回一个Promise对象。因此我们可以进行then的链式执行,这也是解决回调地狱的主要方式。

  1. function fn(num) {
  2. return new Promise(function(resolve, reject) {
  3. if (typeof num == 'number') {
  4. resolve();
  5. } else {
  6. reject();
  7. }
  8. })
  9. .then(function() {
  10. console.log('参数是一个number值');
  11. })
  12. .then(null, function() {
  13. console.log('参数不是一个number值');
  14. })
  15. }
  16.  
  17. fn('hahha');
  18. fn(1234);

then(null, function() {}) 就等同于catch(function() {})

三、Promise中的数据传递

大家自行从下面的例子中领悟吧。

  1. var fn = function(num) {
  2. return new Promise(function(resolve, reject) {
  3. if (typeof num == 'number') {
  4. resolve(num);
  5. } else {
  6. reject('TypeError');
  7. }
  8. })
  9. }
  10.  
  11. fn(2).then(function(num) {
  12. console.log('first: ' + num);
  13. return num + 1;
  14. })
  15. .then(function(num) {
  16. console.log('second: ' + num);
  17. return num + 1;
  18. })
  19. .then(function(num) {
  20. console.log('third: ' + num);
  21. return num + 1;
  22. });
  23.  
  24. // 输出结果
  25. first: 2
  26. second: 3
  27. third: 4

OK,了解了这些基础知识之后,我们再回过头,利用Promise的知识,对最开始的ajax的例子进行一个简单的封装。看看会是什么样子。

  1. var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
  2.  
  3. // 封装一个get请求的方法
  4. function getJSON(url) {
  5. return new Promise(function(resolve, reject) {
  6. var XHR = new XMLHttpRequest();
  7. XHR.open('GET', url, true);
  8. XHR.send();
  9.  
  10. XHR.onreadystatechange = function() {
  11. if (XHR.readyState == 4) {
  12. if (XHR.status == 200) {
  13. try {
  14. var response = JSON.parse(XHR.responseText);
  15. resolve(response);
  16. } catch (e) {
  17. reject(e);
  18. }
  19. } else {
  20. reject(new Error(XHR.statusText));
  21. }
  22. }
  23. }
  24. })
  25. }
  26.  
  27. getJSON(url).then(resp => console.log(resp));

为了健壮性,处理了很多可能出现的异常,总之,就是正确的返回结果,就resolve一下,错误的返回结果,就reject一下。并且利用上面的参数传递的方式,将正确结果或者错误信息通过他们的参数传递出来。

现在所有的库几乎都将ajax请求利用Promise进行了封装,因此我们在使用jQuery等库中的ajax请求时,都可以利用Promise来让我们的代码更加优雅和简单。这也是Promise最常用的一个场景,因此我们一定要非常非常熟悉它,这样才能在应用的时候更加灵活。

四、Promise.all

当有一个ajax请求,它的参数需要另外2个甚至更多请求都有返回结果之后才能确定,那么这个时候,就需要用到Promise.all来帮助我们应对这个场景。

Promise.all接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。

  1. var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
  2. var url1 = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-03-26/2017-06-10';
  3.  
  4. function renderAll() {
  5. return Promise.all([getJSON(url), getJSON(url1)]);
  6. }
  7.  
  8. renderAll().then(function(value) {
  9. // 建议大家在浏览器中看看这里的value值
  10. console.log(value);
  11. })

五、Promise.race

与Promise.all相似的是,Promise.race都是以一个Promise对象组成的数组作为参数,不同的是,只要当数组中的其中一个Promsie状态变成resolved或者rejected时,就可以调用.then方法了。而传递给then方法的值也会有所不同,大家可以再浏览器中运行下面的例子与上面的例子进行对比。

  1. function renderRace() {
  2. return Promise.race([getJSON(url), getJSON(url1)]);
  3. }
  4.  
  5. renderRace().then(function(value) {
  6. console.log(value);
  7. })

嗯,我所知道的,关于Promise的基础知识就这些了,如果还有别的,欢迎大家补充。

转自:http://www.cnblogs.com/tongkaiqiang/

原文链接:http://www.jianshu.com/p/fe5f173276bd

转【前端基础进阶之Promise】的更多相关文章

  1. 前端基础进阶之Promise

    前言 Promise的重要性我认为我没有必要多讲,概括起来说就是必须得掌握,而且还要掌握透彻.这篇文章的开头,主要跟大家分析一下,为什么会有Promise出现. 在实际的使用当中,有非常多的应用场景我 ...

  2. 前端基础进阶(五):全方位解读this

    https://segmentfault.com/a/1190000012646488  https://yangbo5207.github.io/wutongluo/ 说明:此处只是记录阅读前端基础 ...

  3. 前端基础进阶(十三):透彻掌握Promise的使用,读这篇就够了

    Promise的重要性我认为我没有必要多讲,概括起来说就是必须得掌握,而且还要掌握透彻.这篇文章的开头,主要跟大家分析一下,为什么会有Promise出现. 在实际的使用当中,有非常多的应用场景我们不能 ...

  4. 前端基础进阶(十四):es6常用基础合集

    在实际开发中,ES6已经非常普及了.掌握ES6的知识变成了一种必须.尽管我们在使用时仍然需要经过babel编译. ES6彻底改变了前端的编码风格,可以说对于前端的影响非常巨大.值得高兴的是,如果你熟悉 ...

  5. 前端基础进阶(十一):详细图解jQuery对象,以及如何扩展jQuery插件

    早几年学习前端,大家都非常热衷于研究jQuery源码.我还记得当初从jQuery源码中学到一星半点应用技巧的时候常会有一种发自内心的惊叹,“原来JavaScript居然可以这样用!” 虽然随着前端的发 ...

  6. 前端基础进阶(六):在chrome开发者工具中观察函数调用栈、作用域链与闭包

    在前端开发中,有一个非常重要的技能,叫做断点调试. 在chrome的开发者工具中,通过断点调试,我们能够非常方便的一步一步的观察JavaScript的执行过程,直观感知函数调用栈,作用域链,变量对象, ...

  7. 前端基础进阶(七)-前端工程师最容易出错的问题-this关键字

    我们在学习JavaScript的时候,因为对一些概念不是很清楚,但是又会通过一些简洁的方式把它给记下来,那么这样自己记下来的概念和真正的概念产生了很强的偏差. 当然,还有一些以为这个是对的,还会把它发 ...

  8. 前端基础进阶(十五):详解 ES6 Modules

    对于新人朋友来说,想要自己去搞定一个ES6开发环境并不是一件容易的事情,因为构建工具的学习本身又是一个非常大的方向,我们需要花费不少的时间才能掌握它. 好在慢慢的开始有大神提供了一些非常简单易懂,学习 ...

  9. 前端基础进阶(六)-大厂面试题问题:循环闭包与setTimeout

    我在上一篇闭包的文章中留下了一个关于setTimeout与循环闭包的思考题. 利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5 for (var i = 1; i <= ...

随机推荐

  1. [HNOI2009]梦幻布丁(链表+启发式合并)

    洛谷传送门 开始一个O(n^2)思路,每次每句要改变颜色的点,改变完颜色后重新计算颜色的段数,显然拉闸. 然后呢..然后就不会了. 看了别人博客,才知道有个叫做启发式合并的东西,就是把小的合并到大的上 ...

  2. 在RedHat 5下安装Oracle 10g详解(转)

    在RedHat 5下安装Oracle 10g详解(转) Posted on 2012-09-14 13:26 疯狂 阅读(5075) 评论(0)  编辑  收藏 所属分类: database .uni ...

  3. CodeForces - 601A The Two Routes

    http://codeforces.com/problemset/problem/601/A 这道题没想过来, 有点脑筋急转弯的感觉了 本质上就是找最短路径 但是卡在不能重复走同一个点 ----> ...

  4. js9:设置cookie,读取cookie,删除cookie,保存cookie时间,String,Date对象

    原文发布时间为:2008-11-11 -- 来源于本人的百度文章 [由搬家工具导入] <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tran ...

  5. BitmapFactory.Options对图片进行缩放

    package com.pingyijinren.helloworld.activity; import android.graphics.Bitmap; import android.graphic ...

  6. Intersecting Lines--POJ1269(判断两条直线的关系 && 求两条直线的交点)

    http://poj.org/problem?id=1269 我今天才知道原来标准的浮点输出用%.2f   并不是%.2lf  所以wa了好几次 题目大意:   就给你两个线段 然后求这两个线段所在的 ...

  7. Codechef-CHEFPRAD(找事件点+贪心)

    题意: 定义一个函数maxMatching(A,B,y),其输入包含两个整数数组 A 和 B 以及一个整数 y,返回一个整数. 记数组 A 的大小为 N,数组 B 的大小为 M.考虑一个由 {a1, ...

  8. List和Map、Set的区别

    首先 List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合:List 中存储的数据是有顺序,并且允许重复:Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可 ...

  9. 分析Linux文件rwx属性的含义

    Linux上的文件以.开头的文件被系统视为隐藏文件,仅用ls命令是看不到他们的,而用ls -a除了显示 一般文件名外,连隐藏文件也会显示出来. ls -l(这个参数是字母L的小写,不是数字1) 这个命 ...

  10. Eclipse Explorer插件快速打开项目文件在系统资源管理器

    官网:https://github.com/Jamling/eclipse-explorer 安装: 1.在线安装 [Help]->[Eclipse Marketplace]搜索:Eclipse ...