由于JS单线程执行的这个“缺陷”,很多涉及到网络操作和浏览器事件都必须采用异步执行的方法。本人由于我的老师一直推崇RxJS,所以很多时候都是采用RxJS来处理异步事件。但是最近维护公司的老项目的时候,很多时候都是用Promise处理的,所以特意写这篇文章来记录一下我的学习路程,加深自己的理解和印象。(写完这个写RxJS的学习路程 ̄▽ ̄)
1.为什么要使用Promise
Promise通过链式调用方法的形式,用写同步代码的形式去处理异步操作。(这点感觉是JS比较流行的异步编程解决方法的共同特点了,另如RxJS
,Axios
等)
Example:
// 使用Promise前
$.get(params1, data1 => {
console.log(data1);
$.get(data1, data2 => {
console.log(data2);
})
})
复制代码
// 使用Promise后
const get$ = (params) => {
return new Promise((resolve, reject) => {
$.get(params, data => {
resolve(data);
})
})
}
get$(params)
.then(data1 => {
return get$(data1);
}).then(data2 => {
return get$(data2);
}).then(data3 => {
console.log(data3);
})
.catch(err => throw new Error(err));
复制代码
哈哈是不是好理解多了呢!(并不)
既然知道了我们为什么要用Promise,接下来就看看我们要怎么用。
2.Promise是什么?。?
要用一个东西,首先就要理解它是什么。个人比较习惯从源码层面方面去理解一个功能。 业界生产Promise都遵循Promise/A+
标准,传送门:Promise/A+。
下面是掘金里几篇我觉得写得不错的Promise源码解析和实现的文章,有兴趣的同鞋可以去看一下:
- Promise 源码分析
- BAT前端经典面试问题:史上最最最详细的手写Promise教程
- Promise原理讲解 && 实现一个Promise对象 (遵循Promise/A+规范)
- Promise之你看得懂的Promise Promise源码详解
3.Promise一些基础用法和特性
3.1 生成一个Promise对象
const promise = new Promise(resolve, reject) => {
resolve(successData); //'执行成功发出的值'
rejecet(errReason); //'执行失败发错的错误信息'
})
promise.then((successData) => { /*处理成功值*/})
.catch((errReason) => {/*处理错误信息/});
复制代码
一个快速得到Promise对象的写法: Promise.resolve(params), Promise.reject(reason)
Promise.resolve(params);
Promise.reject(reason);
等价于
new Promise((resolve, reject) => {
resolve(params);
reject(reason);
});
复制代码
当然这个方法根据传入的params的类型不同会有不同的表现,想要详细了解避免踩坑的可以看这里:Promise.resolve(), Promise.reject()
若要实现多重回调,只要像第一段里的Example那样,在前一个then中return 一个新的Promise实例就行。当回调链中有reject的Promise时中断回调链执行catch回调。 当前一个then中假如没有return,那么下一个then中接受到值是undefined。
get$(params)
.then(data1 => {
return get$(data1);
}).then(data2 => {
return get$(data2);
}).then(data3 => {
console.log(data3);
})
.catch(err => throw new Error(err));
复制代码
3.2 Promise的一些方法
catch
, then
就不用说了,就是基本用法。finally
不管Promise resolve还是reject之后,总会在then和catch执行之后执行。
Promise.all(:Promise[])
all方法接受一个Promise实例数组作为参数,创建一个新的Promise实例。
该实例当参数内所有的Promise 都resolve之后或参数中不包含 Promise时resolve。
当参数内有一个或以上的Promise reject之后,该实例reject。
let Promise1 = new Promise(function(resolve, reject){ resolve(v1); reject(r1); })
let Promise2 = new Promise(function(resolve, reject){ resolve(v2); reject(r2); })
let Promise3 = new Promise(function(resolve, reject){ resolve(v3); reject(r3); })
let p = Promise.all([Promise1, Promise2, Promise3])
p.then(([v1, v2, v3]) => {
// 三个都成功则成功, 参数为三个Promise成功值构成的数组
}, (r2) => {
// 只要有失败,则失败, 假设Promise2失败
})
复制代码
Promise.race(:Promise[])
race方法接受一个Promise实例数组作为参数,创建一个新的Promise实例。
数组中的的Promise对象为竞争关系, 当参数内有一个或以上的Promise resolve后,该实例resolve。 当参数内有一个或以上的Promise reject之后,该实例reject。
let Promise1 = new Promise(function(resolve, reject){ resolve(v1); reject(r1); })
let Promise2 = new Promise(function(resolve, reject){ resolve(v2); reject(r2); })
let Promise3 = new Promise(function(resolve, reject){ resolve(v3); reject(r3); })
let p = Promise.all([Promise1, Promise2, Promise3])
// 假设Promise1, Promise2 resolve,Promise3 reject,且Promise2 resolve最快
p.then((v2) => {
// v2 比 v1 快, 但因为Promise3 reject了,并不会执行此处回调
}, (r3) => {
// 返回第一个reject的reason
})
复制代码
3.3 Promise的进阶方法
在学习Promise的过程中我看到一篇文章,里面提到了很多Promsie的进阶函数,诸如map
,reduce
,join
等,在我看来跟数组的同名方法行为是一致的,都是对数组的遍历操作,并额外添加对数组遍历的结果添加then和catch方法。简而意之就是快速生成多个Promise对象并添加回调函数。
有兴趣的同鞋到下面链接去看看,我理解有什么不对的地方可以及时指正我,毕竟我对这些方法也只是一知半解。 Javascript 中的神器——Promise
4.Promise应用实例:
下面都是一些我在项目中主要使用到Promise的地方,大家可以看一下,有什么想法都可以跟我交流~
4.1 封装一个基于Promise的网络请求方法:
function httpFetch(url, params) {
// 如果已经出现了错误,则取消掉请求并抛出错误
if(ErrorStatus) {
return new Promsie((resolve, reject) => {
const error = new Error();
error.status = 400 // 根据所需的情况返回状态码
reject(error);
}
}
// 执行网络请求, 并处理返回的结果
return runFetch(url, params).then((response) => {
handleResponse(response, url, params);
});
}
复制代码
// 处理url和params
function prepareFetchData(url, params) {
const method = params.method.toLocaleUpperCase();
let fetchUrl = url;
let options = {};
if(params.data) {
/* HTTP规范没有明确禁止DELETE请求的body,
但是很多服务器的实现会忽略它,所以我们将DELETE和GET请求通过
url query传递数据*/
if(method !== 'GET' && method !== 'DELETE') {
// 这里可能需要预先处理数据,使它符合网络规范
options.body = preTreatData(params.data);
// 或者使用 XMLHttpRequest 与服务器交互
options.xhrData = preTreatData(params.data);
// 上述两种可二选一
} else {
// 通过query传递数据,需预处理data
const query = convertDataToQuery(params.data);
if (query.length) {
fetchUrl = fetchUrl + (fetchUrl.include('?') ? '&' : '?') + query.join('&');
}
}
}
return { url, options },
}
// 与服务器交互获取数据
function runFetch(url, params) {
const paramsData = prepareFetchData(url, params);
// 根据情况选用不同的与服务器交互方法
/* 使用xhrRequest交互, xhrRequest具体实现自己去研究吧~
主要使用到XMLHttpRequest对象的open,onload,onerror,send方法*/
xhrRequest(fetchData.url, fetchData.options)
.then((data) => {
resolve(data);
}, (err) => {
reject(err);
});
// 使用web fetch api, 也是一个Promise对象
fetch(paramsData.url, paramsData.options).then((response) => {
response.text().then((text) => { // Promise again
try {
resolve({
data: JSON.parse(text),
status: response.status,
});
} catch (e) {
resolve({
data: text,
status: response.status,
});
}
}, (err) => {
reject(err);
});
}).catch((error) => {
reject(error);
});
}
复制代码
// 处理请求返回的结果
function handleResponse(response, url, params) {
retrun new Promise((resolve, reject) => {
// 对不同状态码进行特定处理
if (response.status >=200 && response.status < 300) {
resolve(response.data);
}
const error = new Error();
error.status = response.status;
error.data = response.data;
/* 此处可以添加对错误状态码的处理:
如401, 403, 500等*/
reject(error);
});
}
复制代码
// 生成网络请求并使用(例)
function getResponse(data) {
return httpFetch('/api/url', {
// 'GET', 'POST', 'DELETE' 等
method: 'GET',
data,
})
}
复制代码
这样我们就用Promise实现了一个通用的httpFetch
方法,可以添加全局错误处理等~
(后续有在项目中用到的话再添加更多应用例子吧......)
5. 一些替代Promise的写法
5.1 async/await
第一段中我们提到的Promise写法可用es7的语法糖async/await
替代,让它看起来更像同步代码,语法更加简洁。
// 使用async await 替代 Promise
const asyncFunction = async (params) => {
try {
const data1 = await get$(params);
const data2 = await get$(data1);
const data3 = await get$(data2);
console.log(data3);
} catch (error) {
conosle.log(error);
}
}
复制代码
6.拓展阅读
当然这篇文章只是对Promise点到为止,我推荐大家去阮一峰老师的es6专栏中详细的学习一下。 阮一峰es6入门-Promise
下面是涉及到Promise的一些特性和奇奇怪怪的坑的文章,看完之后感觉对Promise理解更深了,也推荐大家去看一下,毕竟开卷有益嘛
好啦这篇没有什么营养的文章到此就结束啦 ~ 接着我会详细写几篇关于自己学习RxJS的文章(毕竟自己最常用的是这个),希望能够帮助到大家学习啦!