JavaScript Promise

前言

假如需要向后端发送一个请求,并对返回的数据进行操作,可能我们第一时间想到的是回调函数。但如果接着又需要执行第二个、第三个...第n个异步操作,那么回调函数就会一层层的嵌套,严重影响了代码可读性和可维护性。

Promise就是解决这个问题的方案,Promise主要做的事情是把回调函数的嵌套逻辑替换成了符合正常人思维习惯的线性逻辑,本文主要介绍Promise的基本用法、API、链式操作、异常处理以及利用promise对数组进行异步操作的方法。

一、杂说

Promise是从DOM中的Futures引入javascript的,理由大概是出现了像NodeJs这样独立于浏览器之外的JavaScript运行环境。在Promise正式被实现之前,部分JS库,像Q、when、WinJS、RSVP.js、jQuery都根据Promises/A+标准分别实现了略有差异的”类Promise“对象。如果你的项目中用到了这些库,不用担心,标准的Promise对象提供了将这些”类Promise“对象转换为标准Promise对象的方法(后文会提到)。关于Promise的兼容性,参考Can I Use

Promise有三种状态:pending、resolved、rejected,状态之间的转换只能从pending到resolved或rejected,并且状态一旦转换就再也无法改变;

Promise的API:

  • Promise的构造器接受一个函数,这个函数接受两个参数:resolved,rejected。

  • promise.then(onResolved, onRejected), 不做赘述;

  • promise.catch(onRejected), promise.then(undefined, onRejected)的语法糖。

  • Promise.resolve(argument),返回一个Promise对象,具体取决于它接受的参数类型。

    • 参数为一个Promise对象,直接返回这个对象;

    • 参数为一个“类promise”对象,将其转化成真正的Promise对象并返回;

    • 参数为其他值,返回一个以参数值作为其resolved函数参数的Promise对象;

  • Promise.reject(obj), 返回一个以参数值(Error的实例)作为其reject函数参数的Promise对象;

  • Promise.all(array), 参数值为Promise数组(也可以包含"类Promise"对象),对数组的每一项调用Promise.resolve(),全部成功则resolved并返回返回值的数组,否则返回第一个rejected的error对象;

  • Promise.race(array), 返回数组中最先resolved或者rejected的那个Promise对象的返回值或者error对象。

二、基本用法

Promise是一个JavaScript对象,它执行在未来的某个时刻才知道结果的操作并返回得到的值或者失败的信息。

// Promise is something like this.  
var promise = new Promise(function(resolved, rejected) {
    doSomethingAsync();
 
    if (success) {
        resolved();
    } else {
        rejected();
    }
})

//How to use a promise. First arg is resolved, second is rejected
promise.then(function(res) {
    console.log(res);
}, function(err) {
    alert(err);
})

三、链式调用

如果仅有一个Promise对象的话,情况较为简单,即在Promise对象被定义时异步操作就开始执行,我们关心的并不是它什么时候执行完毕,而是要在它执行完或者返回错误后对结果进行处理。但当多个Promise要按照一定的顺序执行时,事情就变得复杂起来了。

function fetchSomething() {
    return new Promise(function(resolved) {
        if (success) {
            resolved(res);
        }
    });
}
fetchSomething().then(function(res) {
    console.log(res);
    return fetchSomething();
}).then(function(res) {
    console.log('duplicate res');
    return 'done';
}).then(function(tip) {
    console.log(tip);
})

then函数始终返回一个promise对象,后续的then要等待返回的promise resolve后才能执行,这样就实现了线性逻辑的链式调用。而返回的promise取决于then函数本身return的值。如果return值本身就是一个promise对象,则替代默认的promise对象作为返回值;如果return值为其他值,则将这个值作为返回的promise的resolve函数的参数值。

四、异常处理

从上面的代码可以看出,then函数接受两个参数:resolved、rejected。上面没写rejected是因为rejected函数是可选的,当然也可以在then之后写catch,.catch(rejected)本质上是.then(undefined, rejected)的语法糖。
这两种方式是有区别的,.then(resolved, rejected)只能捕获之前的promise的异常,而写在其后的.catch(undefined, rejected)还可以捕获其resolved函数产生的异常。另外只要Promise链中有一个promise对象抛出异常,其后所有的resolved都被跳过,直到这个异常被rejected或者catch处理。

五、排序

当需要用数组的数据执行异步操作,因为数组的遍历方法forEach、map等都是同步的,所以结果的顺序就取决于异步操作完成的顺序,如果对顺序有要求,这样就不尽人意。

// 假设fetchID返回一个Promise对象
names.forEach(function(name) {
    fetchID(name).then(function(id) {
        renderInfo(id);
    })
})

这个时候就需要利用then()来制定顺序:

names.reduce(function(sequence, name) {
    return sequence.then(function() {
        return fetchID(name);
    }).then(function(id) {
        renderID(id);
    })
}, Promise.then())

因为此时先遍历的name处理的结果将作为后面的sequence,构成了链式关系,就避免了下载速度决定顺序的问题。但仍然可以优化:因为此时的ID是获取一个,render一个的。如果能够先获取所有的ID再逐条渲染的话,性能会更好。

Promise.all(names.map(fetchID))
       .then(function(IDs) {
           IDS.forEach(function(id) {
               renderID(id);    //同步
           })
       })
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章