Promise 就是这么简单
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); }); }// 调用requestrequest(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,就是这么简单。
作者:爱coding的husky
链接:https://www.jianshu.com/p/95025ed6cf2e
共同学习,写下你的评论
评论加载中...
作者其他优质文章