Promise 就是这么简单

作者: 清晨细雨霏 | 来源:发表于2018-05-31 10:17 被阅读6次

    Promise 是干嘛的?

    Promise是ES6针对js异步编程一种解决方案,也解决了ES5之前异步编程大量回调函数的写法的痛点,我们来亲切感受一下。

    假设有这么一个需求:一个页面需要进行4次ajax请求才能渲染完所有内容,但是每一次请求依赖上一次请求返回的动态url。我们会联想到这将是一次链式请求。

    ES5写法:

    // request 假设是事先封装好的ajax方法
    
    request(url, function(res1) {
        /* 大量业务逻辑 */
        ... 
        request(res1.url, function(res2) {
            /* 大量业务逻辑 */
            ... 
            request(res2.url, function(res3) {
                /* 大量业务逻辑 */
                ... 
                request(res3.url, function(res4) {
                    /* 大量业务逻辑 */
                    ... 
                });
            });
        });
    });
    

    看到这样的异步回调,是不是有一种在地狱间来回穿梭的感觉,这就是传说中的“回调地狱”,代码层级嵌套深,结构很不直观,增加大量的维护成本(当然谁会去想维护这样的代码?,何以解忧,唯有离职)-0—。

    ES6写法:

    // request 假设是事先封装好的ajax Promise 方法
    
    request(url, (res) => {
        /* 大量业务逻辑 */
        ... 
        return request(res.url);
    }).then((res) => {
        /* 大量业务逻辑 */
        ... 
        return request(res.url);
    }).then((res) => {
        /* 大量业务逻辑 */
        ... 
        return request(res.url);
    }).then((res) => {
        /* 大量业务逻辑 */
        ... 
    });
    

    领教了ES5回调地狱,回头看看这ES6的Promise写法,是不是觉得优雅不要太多,链式调用清晰明了,结构分明(当然这是我个人感觉了,你觉得呢?)-0-。

    Promise 基本用法

    Promise 其实就是一个构造函数,它接收一个回调函数作为一个参数,这个回调函数默认有两个参数,即resolve和reject。进行new后,返回一个promise对象,这个对象即有then,catch,finally的Promise原型方法。

    1.创建一个Promise

    我们先来一个最基本的promise:

    // 创建一个promise对象
    var promiseObj = new Promise((resolve, reject) => {
        /* 业务逻辑 */
        ... 
        if (true) {
            // success
            resolve(success);
        } else {
            reject(error);
        }
    });
    
    // 调用promise对象方法
    promiseObj.then((success) => {
        // 此处为成功状态的回调,success数据即为以上resolve函数里的success
    }).catch((e) => {
        // 此处为失败状态的回调, error即为以上reject函数里的error;
    }).finally(() => {
        // 此处不管成功还是失败,都会执行,使用情况比较少。
    });
    


    在创建一个promise对象的时候,Promise构造函数里的参数即回调函数,它会执行一遍,所以我们通常会将new Promise的过程放在一个方法里作为return返回,在需要这个promise实例的时候,调用这个方法即可获取到这个实例并执行代码业务逻辑,具体如下。

    2.封装我们想要的Promise

    我们来写一个文章开头提到的Promise的request方法,深刻感受一下:

    function request(url) {
        return new Promise((resolve, reject) => {
            var XHR = new XMLHttpRequest();
            XHR.open('GET', url);
            XHR.onreadystatechange = function () {
                if (XHR.readyState === 4) {
                    if (XHR.status === 200) {
                        // 请求成功,将服务器返回的数据reslove出去
                        resolve(XHR.responseText);
                    } else {
                        // 请求失败,将触发的错误reject出去
                        reject(new Error(XHR.responseText));
                    }
                } 
            };
            XHR.send(null);
        });
    }
    
    // 调用request
    request(url).then((res) => {
        // res 即为 以上resolve函数里面的XHR.responseText;
        console.log(res);
    }).catch((e) => {
        // catch 通常即为捕获错误的地方,即reject返回的new Error(XHR.responseText)
        new Error(e);
    });
    

    Promise 实例方法

    在Promise实例化一个promise对象后,它只有三个方法,即 then, catch, finally。

    1.then

    then 方法里执行的是resolve状态

    request(url).then((res) => {
        // res 为resolve状态返回的数据,一般也指成功状态
    })
    

    then 方法可以链式调用,即文章开头的request方法链式then调用

    request(url).then((res) => {
        ... 
        return res.data;
    }).then((data) => {
        // 此处的data 即为上一步then的return值:res.data
    });
    

    所以then的return值也可以是一个promise对象:

    request(url, (res) => {
        ... 
        // 返回request方法调用结果:即一个promise对象
        return request(res.url);
    }).then((res) => {
        // 此处的res 即为上一步返回方法request里的的resolve值,相当于上一步返回request调用then
        ... 
        // 继续返回一个promise对象
        return request(res.url);
    }).then((res) => {
        
    })
    

    2.catch

    catch 方法里执行的是reject状态和错误处理

    request(url).then((res) => {
    
    }).catch((e) => {
        // reject状态或者request方法内部错误了,都会在这里被捕获到。
    });
    

    说白了,catch方法不仅处理reject状态数据,而且还会捕获因为代码运行而产生的错误,我们就叫它错误垃圾处理箱吧。

    3.finally

    finally 方法即不管resolve还是reject状态都会执行的方法。

    request(url).then((res) => {
    
    }).catch((e) => {
    
    }).finally(() => {
        console.log('请求完成了');
    });
    

    Promise 静态方法

    1.Promise.all()

    假设一个页面有多个并行的请求,但是你想等他们都请求完后再统一获取他们返回的数据并处理,就可以用到这个方法。
    此方法是将多个Promise实例包装成一个新的Promise实例。
    Promise.all 方法接收一个数组作为参数,数组的值即为各个promise实例:

    var promises = Promise.all([p1, p2, p3]);
    
    promises.then((resArr) => {
        // resolve状态 此时返回的resArr为一个数组,数组值为p1,p2,p3的resolve数据
    }).catch((e) => {
        // reject 状态 p1,p2,p3的reject
    });
    

    如上,Promise.all将promise实例p1,p2,p3包装成一个新的promise实例promises,此时promises的回调方法返回参数会发生一些改变:

    • promises的resolve状态由p1,p2,p3共同决定,只有p1,p2,p3状态都为resolve了,promises的状态才会变为resolve
    • promises的reject状态由p1,p2,p3任何一个决定,只要p1,p2,p3的状态某一个为reject了,promises状态就会为reject

    我们可以通过多个request方法来深刻了解一下:

    // requests为三个不同的请求被Promise.all包装成功一个新的promise实例
    var requests = Promise.all([request(url1), request(url2), request(url3)]);
    
    // 调用requests,会同时发出三个请求
    requests.then((res) => {
        // resovle状态:当三个请求都成功以后,会进入此状态
        // 此时res 为一个数组,即三个请求返回的数据组成的数组
        console.log(res.length) // 3
    }).catch((e) => {
        // reject状态: 只要三个请求其中一个发生了错误,就会进入此状态
    });
    

    2.Pormise.race()

    Promise.race 同Promise.all一样,也是将多个promise实例包装成一个新的promise实例:

    var promises = Pormise.race([p1, p2, p3]);
    

    但是,它跟Promise.all不同的是,只要p1,p2,p3中谁最先变为状态(不管是resolve状态还是reject状态),promises回调函数即为它的状态。
    我们先看看resolve状态:

    promises.then((res) => {
        // 假设p1,p2,p3中p2最先改变为resolve状态,res参数即为p2的resolve数据
    });
    

    再看看reject状态的运用:

    // 请求超时promise  设置5秒超时
    function timeout () {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject(new Error('timeout'));
            }, 5000);
        });
    }
    
    var requests = Promise.race([request(url), timeout()]);
    requests.then((res) => {
        // 如果5秒之内成功请求,此时即为request的resolve状态
    }).catch((e) => {
        // 如果5秒超时,此时即为timeout的 reject状态
    });
    

    3.Promise.resolve() 与 Promise.reject()

    这两种方法都是将现有对象转为Promise对象,区别是该对象是否有then方法,有then方法则直接调用该对象的then方法,如果没有,则直接返回这个对象。
    这两种方法我其实用的比较少,不过我们可以改造一下request方法来熟悉这两个方法:

    // request2方法做了一些改变, 直接返回一个具有then方法和请求结果的对象。
    function request2 (url) {
        var data = null;
        var XHR = new XMLHttpRequest();
        XHR.open('GET', url);
        XHR.onreadystatechange = function () {
            if (XHR.readyState === 4) {
                if (XHR.status === 200) {
                    data = XHR.responseText;
                } 
            } 
        };
        XHR.send(null);
    
        return {
            data,
            then (resolve, reject) {
                resolve(this.data);
            }
        }; 
    }
    
    // 理由Promise.resolve 转化request2为promise对象
    var promise = Promise.resolve(request2(url));
    promise.then((res) => {
        // 此时 res为 request2返回对象then方法的返回值 this.data
    });
    

    Promise.reject 与之是同样的用法,但是与Promise.resolve的区别是reject状态返回的不是reject数据,而是这个request2返回的对象本身:

    function request2 (url) {
        ... 
        return {
            data,
            then (resolve, reject) {
                reject('出错了');
            }
        }
    }
    
    var thenObj = request2(url);
    var promise = Promise.reject(thenObj);
    promise.then((res) => {
    
    }).catch((e) => {
        // e即为thenObj对象本身,并不是 reject 数据'出错了'
        console.log(e === thenObj) //true
    })
    

    总结

    通过Promise构造函数封装我们的异步业务逻辑,再进行优雅的链式then调用,跳出“回调地狱”,再在垃圾箱catch中捕获你的错误,Promise,就是这么简单。

    相关文章

      网友评论

        本文标题:Promise 就是这么简单

        本文链接:https://www.haomeiwen.com/subject/gvafsftx.html