让前端飞Web前端之路前端开发那些事

Promise 就是这么简单

2018-05-31  本文已影响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的回调方法返回参数会发生一些改变:

我们可以通过多个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,就是这么简单。

上一篇下一篇

猜你喜欢

热点阅读