zoukankan      html  css  js  c++  java
  • ES6笔记(7)-- Promise异步编程

    系列文章 -- ES6笔记系列

    很久很久以前,在做Node.js聊天室,使用MongoDB数据服务的时候就遇到了多重回调嵌套导致代码混乱的问题。

    JS异步编程有利有弊,Promise的出现,改善了这一格局,让异步编程表现出类似“同步式代码”的形式,更好地体现了它的价值。

    一、基本概念

    1. Promises/A+规范

    Promise是一种异步编程的解决方案,本质来说其实它是一种规范,Promises/A+规范

    根据规范的定义,一个Promise对象应该至少有以下的基本特点

    三个状态

    Promise有三个状态:Pending(进行中)Resolved或Fulfilled(已完成)Rejected(已失败)

    其中:Pending为Promise的初始状态;当Resolved成功时,会调用onFulfilled方法;当Rejected失败时,会调用onRejected方法

    并且:状态只能从Pending转换为Resolved状态,或者从Pending转换为Rejected状态,不存在其他状态间的转换

    Then方法

    Promise必须提供一个then方法,用以访问当前值、最终的返回值以及失败的原因

    最基本的then方法接受两个函数参数 promise.then(onFulfilled, onReject),对应于状态变化到Resolved和Rejected时的函数调用

    2. Promise简单的实现

    基于Promises/A+中规范的要求,可以自行实现一个基本的promise对象

    可参考 一步一步实现一个Promise

    二、基本使用

    1. 使用相关插件

    近年来,已经出现了很多Promise异步编程的插件,我们可以使用这些插件,常见的有:

     

    例如使用jQuery新版Ajax模块内置的Deferred使用到了Promise,我们可以直接这样调用

    // 回调函数的方式
    $.get('url', function(rs) {
        rs = JSON.parse(rs);
    });
    
    // Promise的形式
    $.get('url').success(function(rs) {
        rs = JSON.parse(rs);
    })

    不过jQuery中的Promise并不是完全按照Primises/A+规范来实现的,所以使用的时候可能会有问题,详见

    2. 原生的Promise支持

    ES6原生引入了Promise,它在很多现代浏览器上已经得到支持

    在不支持原生Promise的环境下,除了可以直接使用一些第三方Promise库之外,还可以使用这个插件来兼容低版本浏览器

    其实,ES6中的原生Promise实现与RSVP.js有很大的关系,所以相关语法也和它类似

    比如在爬虫开发时,先获取用户资料,再获取该用户的文章,则可以用Promise,用类似以下的结构实现

    function getUser(id) {
        return new Promise(function(resolve, reject) {
            $.get('/user?id=' + id, function(rs) {
                rs = JSON.parse(rs);
    
                if (rs.status !== 200) {
                    reject(rs);
                } else {
                    resolve(rs);
                }
            });
        });
    }
    
    function getContent(user) {
        return new Promise(function(resolve, reject) {
            $.get('/content', {
                user: user
            }, function(rs) {
                rs = JSON.parse(rs);
    
                if (rs.status !== 200) {
                    reject(rs);
                } else {
                    resolve(rs);
                }
            });
        });
    }
    
    getUser(11).then(function(rs) {
        return getContent(rs.user);
    }).catch(function(rs) {
        throw Error(rs.msg);
    }).then(function(rs) {
        console.log(rs.content);
    }).catch(function(rs) {
        throw Error(rs.msg);
    });

    成功调用getUser之后,可以通过return 返回getContent(rs.user)这个promise对象,继续接下去的执行任务

    除了直接返回这个新的promise对象,我们也可以直接返回一个数据,这个数据将会作为下一函数调用时的参数,且看例子:

    function step(num) {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                if (num > 0) {
                    resolve(num);
                } else {
                    reject(0);
                }
            })
        });
    }
    
    step(-1).then(function(num) {
        console.log('resolve ' + num);
        return -5;
    }, function(num) {
        console.log('reject ' + num); // reject 0
        return 5;
    }).then(step) // 下一个要执行的任务操作
        .then(function(num) {
        console.log('resolve ' + num); // resolve 5
    }, function(num) {
        console.log('reject ' + num);
    });

    当参数的数值为正数时,则直接resolve返回该数值,如果为负数则reject返回0,初始数值为-1,所以调用了reject

    再看另一个例子:

    function log(n) {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
    
                if (n % 2) {
                    resolve('奇数:' + n);
                } else {
                    reject('偶数:' + n);
                }
                 
            }, 1000);
        });
    }
    
    log(1).then(function(data) {
        console.log(data);
        return log(2);
    }, function(err) {
        console.log(err);
        return log(3);
    }).then(function(data) {
        console.log(data);
    }, function(err) {
        console.log(err);
    });

    以上代码执行之后

    下面来详细介绍原生Promise的使用方法

    new Promise(func)

    通过实例化构造函数成一个promise对象,构造函数中有个函数参数,函数参数为(resolve, reject)的形式,供以函数内resolve成功以及reject失败的调用

    .then(onFulfilled, onRejected)

    then方法,方法带两个参数,可选,分别为成功时的回调以及失败时的回调

    如上代码,log(1)时执行了resolve,log(2)时执行了reject 

    .catch(onRejected)

    catch方法,方法带一个参数,为失败时的回调。其实.catch方法就是 .then(undefined, onRejected)的简化版,通过例子看看它的特点

    function log(n) {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
    
                if (n % 2) {
                    resolve('奇数:' + n);
                } else {
                    reject('偶数:' + n);
                }
                 
            }, 1000);
        });
    }
    
    log(2).then(function(data) {
        console.log(data);
        return log(3);
    }).catch(function(err) {
        console.log(err);
    });

    看这个例子,then中只有一个参数,调用log(2)之后reject执行,到达catch中输出

    再看一个栗子,代码换成以下两种,输出都一样

    log(1).then(function(data) {
        console.log(data);
        return log(2);
    }).catch(function(err) {
        console.log(err);
    });
    log(1).then(function(data) {
        console.log(data);
        return log(2);
    }).then(undefined, function(err) {
        console.log(err);
    });

    如此一来,第一轮log(1)的resolve后,自行调用log(2),从而执行reject,通过catch执行相应的输出

    Promise.all()方法

    Promise.all()方法接受一个promise的数组对象,只有数组中所有的promise都执行成功,整个promise才算成功,如果数组对象中有个promise执行失败,则整个promise就失败

    看这个简单的例子,意图是调用log(1,2,3,4,5)这个promise完成之后再调用log(6),其中相应值小于3就resolve,反之就reject

     1 function log() {
     2     var promises = [];
     3 
     4     [...arguments].forEach(function(n) {
     5          promises.push(new Promise(function(resolve, reject) {
     6             
     7             var info = '';
     8             setTimeout(function() {
     9                 if (n < 3) {
    10                     info = '小于3 resolve:' + n;
    11                     console.log(info);
    12                     resolve(info);
    13                 } else {
    14                     info = 'reject:' + n;
    15                     console.log(info);
    16                     reject(info);
    17                 }
    18             }, 1000);
    19         }));
    20     });
    21 
    22    return Promise.all(promises);
    23 }
    24 
    25 log(1, 2, 3, 4, 5).then(function(data) {
    26     console.log(data);
    27     return log(6);
    28 }).catch(function(err) {
    29     console.log(err);
    30 });

    首先,依次将相应实例化的promise对象存入promises数组,通过Promise.all()调用返回,执行结果为

    由输出结果知,1和2被resolve,3、4、5被reject,整个数组里已经有多于一个的promise对象被reject,仅仅触发了catch中的回调,所以log(6)得不到执行

    Promise.race()方法

    与Promise.all()类似,它也接受一个数组对象作为参数,但其意义不一样

    只有数组中所有的promise都执行失败,整个promise才算失败,如果数组对象中有个promise执行成功,则整个promise就成功

    把上述代码的all换成race,执行结果为:

    由输出结果知,1和2被resolve,3、4、5被reject,整个数组里已经有多于一个的promise对象被resolve,触发了then中成功的回调,log(6)得到调用执行

    因为这时还没有额外的then或catch方法来监视log(6)的状态,所以仅仅输出的在log函数中执行的结果

    Promise.resolve()方法

    除了在实例化Promise构造函数内部使用resolve之外,我们还可以直接调用resolve方法

    var promise = Promise.resolve('resolve one');
    // var promise = Promise.reject('reject one');
    
    promise.then(function(data) {
        console.log(data); // resolve one
    }).catch(function(err) {
        console.log(err); 
    });

    参数除了可以直接指定值之外,还可以是一个Promise实例,具有then方法的对象,或者为空

    参数为Promise实例,则将包装后返回该Promise实例

    var promise = Promise.resolve($.get('url'));

    前文说到jQuery的Promise实现方式并不是完全按照规范来着,通过Promise.resolve的包装,可以返回一个规范化的Promise实例

    参数为空,则直接返回一个状态为resolved|fulfilled的Promise对象

    setTimeout(function () {
      console.log('three');
    }, 0);
    
    Promise.resolve().then(function () {
      console.log('two');
    });
    
    console.log('one');

    直接resolve的Promise对象是在本轮事件循环结束时执行,setTimeout是在下一轮事件循环结束时执行,所以输出为:

    参数为一个具有then方法的对象,则自动将这个对象转换为Promise对象并调用该then方法,如

    Promise.resolve({
        then: function(resolve, reject) {
            resolve('resolved');
        }
    }).then(function(data) {
      console.log(data); // resolved
    }).catch(function(err) {
        console.log(err);
    });

    Promise.reject()方法

    除了在实例化Promise构造函数内部使用reject之外,我们还可以直接调用reject方法

    类似于Promise.resolve()中参数的多样化,且看以下几个栗子:

    Promise.resolve({
        then: function(resolve, reject) {
            reject('rejected');
        }
    }).then(function(data) {
      console.log(data);
    }).catch(function(err) {
        console.log(err); // rejected
    });
    setTimeout(function () {
      console.log('three');
    }, 0);
    
    Promise.reject().catch(function () {
      console.log('two');
    });
    
    console.log('one'); 
    
    // one
    // two
    // three
    var promise = Promise.reject($.get('url'));
    // var promise = Promise.resolve('resolve one');
    var promise = Promise.reject('reject one');
    
    promise.then(function(data) {
        console.log(data);
    }).catch(function(err) {
        console.log(err); // reject one
    });

    3. Promise的反模式

    关于Promise有很多难点技巧点,比如以下四中调用方式的区别

    doSomething().then(function () {
        return doSomethingElse();
    });
    
    doSomethin().then(functiuoin () {
        doSomethingElse();
    });
    
    doSomething().then(doSomethingElse());
    
    doSomething().then(doSomethingElse);

    相关解释见:谈谈使用 promise 时候的一些反模式

  • 相关阅读:
    COS和CDN的关系
    【转】WebGL 着色器和GLSL
    【转】前端最新性能指标
    【转】理解音视频 PTS 和 DTS
    HLS
    【转】带有function的JSON对象的序列化与还原
    环信Demo 导入错误
    安卓中 使用html来使文字变色Html.fromHtml
    第三方下载控件 用起来还是不错的偶!Aria
    网络文件下载
  • 原文地址:https://www.cnblogs.com/imwtr/p/5916793.html
Copyright © 2011-2022 走看看