前言
查看之前的文章
目录
- Promise 的用途
- 如何创建一个 new Promise
- 如何使用 Promise.prototype.then(可查 MDN)
- 如何使用 Promise.all(可查 MDN)
- 如何使用 Promise.race(可查 MDN)
以AJAX的封装为例来解释Promise的用法
拓展:析构赋值
cosnt success = options.success
cosnt fail= options.fail
等价于优化
cosnt {success,fail} = options
- AJAX定义
ajax = (method,url,options) =>{
cosnt {success,fail} = options
const request = new XMLJttpRequest()
request.open(method,url) //method:方法;url:地址
request.onreadystatecharge = ()=>{
if(request.readyState === 4){//readyState:准备状态 4为准备完成发送状态码(可能发送成功的也可能发送失败)的
if(request.status<400){ //成功就调用success(小于400)
success.call(null,request.response)
}else if(request.status>=400){//失败就调用fail(大于等于400)
fail.call(null,requset,request.status)
}
}
}
request.send()
}
AJAX使用
特地:使用 两种调用进行演示
ajax('get','/xxx',{
success:(response){}, //成功:function缩写
fail:(request,status)=>{}//失败:箭头函数 ES6的新语法!!!重要
})
Promise说这个代码太傻了
我们改用Promise写法
step1:先改调用姿势
//老版调用
ajax('get','/xxx',{
success:(response){}, //成功:function缩写
fail:(request,status)=>{}//失败:箭头函数 ES6的新语法!!!重要
})
//新版Promise写法调用
ajax('get','/xxx',{
.then((response)=>{},(request,status)=>{})//因为失败只接受一个参数,所以status会被忽略,为无效参数
})
- 虽然也是回调
- 但是不需要记success 和 fail 了
- then的第一个参数就是success
- then的第二个参数就是fail
请问ajax()返回了啥?
返回了一个含有.then()方法的对象呗
那么在请问如何得到这个含有.then()的对象呢?
那就要改造ajax的源码了
step2:改造ajax源码
ajax = (method,url,options) =>{
return new Promise ((resolve,reject)=>{ //一进来就return
cosnt {success,fail} = options
const request = new XMLJttpRequest()
request.open(method,url) //method:方法;url:地址
request.onreadystatecharge = ()=>{
if(request.readyState === 4){//readyState:准备状态 4为准备完成发送状态码(可能发送成功的也可能发送失败)的
if(request.status<400){ //成功就调用success(小于400)
resolve.call(null,request.response) //决定(成功)
}else if(request.status>=400){//失败就调用fail(大于等于400)
reject.call(null,requset,request.status) //拒绝(失败)
}
}
}
request.send()
})
}
Promise 的用途
Promise与异步关联
Promise 是一个对象,用于表示一个异步操作的最终完成 (或失败), 及其结果值.(MDN)
本质上Promise是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。
![02ea0943b29bf0dc6ca4b7954bf32694.png](https://img-blog.csdnimg.cn/img_convert/02ea0943b29bf0dc6ca4b7954bf32694.png)
案例代码:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 300);
});
promise1.then((value) => {
console.log(value);
// expected output: "foo"
});
console.log(promise1);
// expected output: [object Promise]
依次打印
> [object Promise]
> "foo"
通过以上代码可以看出Promise关于异步(setTimeout)的用法
因为设置了定时器,需要等待300ms后才会return value
异步并不需要等待(并不会阻塞代码),可以先往下执行,首先打印出 [object Promise]
等待300ms后setTimout返回value给Promise1,然后Promise1通过then函数打印出 foo
如何创建一个 new Promise
Promise 最重要的五个单词
return new Promise ((resolve,reject)=>{})
背过这五个单词即可,等你用熟了
小结
step1
- return new Promise ((resolve,reject)=>{})
- 任务成功则调用resolve(result)
- 任务失败则调用reject(error)
- resolve 和 reject 会再去调用成功和失败函数
step2
- 使用.then(success,fail)传入成功和失败函数
- 点到为止(先讲到这里,Promise还有高级用法,以后说)
我们封装的ajax的缺点
post无法上传数据
- request.send(这里可以上传数据)
不能设置请求头
- request.setRequestHeader(key,value)
怎么解决呢?
- 花时间把ajax写到完美
- 使用jQuery.ajax(这个可以)
- 使用axios(这个库比jQuery逼格高)
jQuery.ajax
已经非常完美
- 进入jQuery的文档,搜索ajax,找到jQuery.ajax
- 看看参数说明,然后直接看代码示例
- 看看jQuery的封装,就知道自己的封装辣鸡
封装优点
- 支持更多形式的参数
- 支持Promise
- 支持的功能超多
我们需要掌握jQuery.ajax吗
- 不用,现在的专业前端在用axios
- 写篇博客罗列下功能即可
axios(重要)
目前最新的AJAX库
- 显然他抄袭了jQuery的封装思路
- 写篇博客(参考PDF方方博客)
axios.get('/5.json') //地址:本地路径
.then(response => //成功
console.log(response)
)
axios高级用法
JSON自动处理
- axios如发现响应的Content-Type是json
- 就会自动调用JSON.parse
- 所以说正确设置Content-Type是好习惯
请求拦截器
你可以在所有请求里加些东西,比如加查询参数
响应拦截器
你可以在所有响应里加些东西,甚至改内容
- 可以生成不同实例(对象)
不同的实例可以设置不同的配置,用于复杂场景
封装!封装!封装!
- 初级程序员学习API(包括Vue/React的API)
- 中级程序员学习如何封装
- 高级程序员造轮子
总结
- 异步是什么
- 异步为什么会用到回调
- 回调有哪些三个问题:地狱,名字,错误处理
- Promise是什么:1976年的一种设计模式
- 如何使用Promise:背下来五个词
- 如何使用Axios:发个请求试用看看
- Promise是前端解决异步问题的统一方案(面试必考)
![b2f8fef192e64eb46a0b27ea293c1c74.png](https://img-blog.csdnimg.cn/img_convert/b2f8fef192e64eb46a0b27ea293c1c74.png)
![b2f8fef192e64eb46a0b27ea293c1c74.png](https://img-blog.csdnimg.cn/img_convert/b2f8fef192e64eb46a0b27ea293c1c74.png)
![b2f8fef192e64eb46a0b27ea293c1c74.png](https://img-blog.csdnimg.cn/img_convert/b2f8fef192e64eb46a0b27ea293c1c74.png)
![c5c349340f2c1ebc2e8de0a5ff57bf03.png](https://img-blog.csdnimg.cn/img_convert/c5c349340f2c1ebc2e8de0a5ff57bf03.png)
![329e5dc172861d329263d4ce05124297.png](https://img-blog.csdnimg.cn/img_convert/329e5dc172861d329263d4ce05124297.png)
![ed6b83fbfbf13cf606d1a0c789eb5b8a.png](https://img-blog.csdnimg.cn/img_convert/ed6b83fbfbf13cf606d1a0c789eb5b8a.png)
![266fd88adb2293aa142f1c631723cda1.png](https://img-blog.csdnimg.cn/img_convert/266fd88adb2293aa142f1c631723cda1.png)
![d731dd186bea57b1ea711ee93e3d765f.png](https://img-blog.csdnimg.cn/img_convert/d731dd186bea57b1ea711ee93e3d765f.png)
![d731dd186bea57b1ea711ee93e3d765f.png](https://img-blog.csdnimg.cn/img_convert/d731dd186bea57b1ea711ee93e3d765f.png)
![b2f8fef192e64eb46a0b27ea293c1c74.png](https://img-blog.csdnimg.cn/img_convert/b2f8fef192e64eb46a0b27ea293c1c74.png)
![b2f8fef192e64eb46a0b27ea293c1c74.png](https://img-blog.csdnimg.cn/img_convert/b2f8fef192e64eb46a0b27ea293c1c74.png)
降低耦合度方面
一个库依赖另一个库或者另外几个库难以避免。
但是依赖太多,就难以独立。
如果说,某些功能调用确实是存在调用其它功能的地方,可以利用回调函数、事件通知这些方式来降低耦合,不同的复杂度可以用不同的方法。
(如果封装的功能过于复杂,其中的事件通知部分甚至可以扩展到“消息总线”的机制)
比如说封装的时候:
① 某一功能调用很简单,可以直接出结果(像 1+1 = 2 这种),就直接使用返回值的方式。
② 如果某个功能需要有一定的时间进行处理,或者调用其它的功能(定义好参数、返回值),可以用回调的方式。
③ 如果涉及到多个状态的变化,或者多次调用,可以通过事件(或者消息总线)的方式。
这里提一点,如果回调的层次过多,有可能会陷入js中著名的回调地狱(callback hell)。
这是使用的回调时候需要特别注意的地方。
当然,我们也可以用 Promise 的机制来避免这一点。(Promise是 ES6 中引入的新语法,老的浏览器可以使用 jQuery 中的 $.Deferred 来替代,也有一些其他的封装)
其实,Promise机制同字面上的意思,就是先给你个承诺(返回值对象),当有结果的时候再给你(回调)。
比如说:
doSomething()
.then(doSomething_2())
.thne(doSOmething_3());
(注意:同样 Promise 用不好,也很坑;不管是回调,还是 Promise 都只是一个方法而已,关键是你怎么用)
如何使用 Promise.prototype.then
then是Promise实例对象的一个属性函数,返回的是一个Promise对象,有两个参数,一个是成功的回调函数,一个是失败的回调函数
then()
方法返回一个Promise
。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
const promise1 = new Promise((resolve, reject) => {
resolve('Success!');
});
promise1.then((value) => {
console.log(value);
// expected output: "Success!"
});
使用 then
方法
var p1 = new Promise((resolve, reject) => {
resolve('成功!');
// or
// reject(new Error("出错了!"));
});
p1.then(value => {
console.log(value); // 成功!
}, reason => {
console.error(reason); // 出错了!
});
如何使用 Promise.all
返回的是Promsie对象,参数可以是Promise实例对象的数组
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
具体代码如下:
let p1 = new Promise((resolve, reject) => {
resolve('成功了')
})
let p2 = new Promise((resolve, reject) => {
resolve('success')
})
let p3 = Promse.reject('失败')
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error)
})
Promise.all([p1,p3,p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 失败了,打出 '失败'
})
Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。
代码模拟:
let wake = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${time / 1000}秒后醒来`)
}, time)
})
}
let p1 = wake(3000)
let p2 = wake(2000)
Promise.all([p1, p2]).then((result) => {
console.log(result) // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
console.log(error)
})
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
在实际项目中,可能会遇到 需要从前两个接口中的返回结果获取第三个接口的请求参数这种情况。 也就是需要等待两个/多个异步事件完成后,再进行回调。
对于异步回调,首先想到的就会是使用Promise封装,然后使用.then()来触发回调。那么对于两个或多个异步事件均完成后再触发回调可以使用Promise.all()方法。**Promise.all(iterable)**
方法返回一个 Promise
实例,此实例在 iterable
参数内所有的 promise
都“完成(resolved)”或参数中不包含 promise
时回调完成(resolve);如果参数中 promise
有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise
的结果。
Promise.all(iterable)
方法返回一个 Promise
实例,此实例在 iterable
参数内所有的 promise
都“完成(resolved)”或参数中不包含 promise
时回调完成(resolve);如果参数中 promise
有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise
的结果。
它通常在启动多个异步任务并发运行并为其结果创建承诺之后使用,以便人们可以等待所有任务完成。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
![5129209cda44a61ee1f5fa40166f7c91.png](https://img-blog.csdnimg.cn/img_convert/5129209cda44a61ee1f5fa40166f7c91.png)
1.并发执行
var p1 = Promise.resolve(1);
var p2 = Promise.resolve(2);
var p3 = Promise.resolve(3);
//
Promise.all([p1, p2, p3]).then(function(results){
console.log(results);
});
/**
[ 1, 2, 3 ]
*/
2.并发执行,出错一个,将会触发catch
var p1 = Promise.resolve(1);
var p2 = Promise.reject(2); //失败的原因,catch将会捕获这个值
var p3 = Promise.resolve(3);
//
Promise.all([p1, p2, p3]).then(function(results){
console.log(results);
}).catch(function(e){
console.log(JSON.stringify(e));
});
/**
2
*/
.可以看出只要有一个地址出错,那么所有的就都不会输出
var http = require("http");
//
function getURL(url){
return new Promise(function(resolve, reject){
http.get(url, function(res){
resolve(res);
}).on("error", function(e){
reject(e);
});
});
}
//
var itbilu = getURL("http://itbilu.com");
var yijiebuyi = getURL("http://yijiebuyi111.com");
Promise.all([itbilu, yijiebuyi]).then(function(results){
results.forEach(function(result){
console.log(result.statusCode);
});
}).catch(function(err){
console.log(err);
});
/**
1)正常情况下输出:
301
200
2)故意拼错一个地址(itbilu拼错):
var itbilu = getURL("http1://itbilu.com");
[Error: Protocol "http1:" not supported. Expected "http:".]
3.访问不存在的地址
var yijiebuyi = getURL("http://yijiebuyi.com1");
{ [Error: getaddrinfo ENOTFOUND yijiebuyi.com1 yijiebuyi.com1:80]
code: 'ENOTFOUND',
errno: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'yijiebuyi.com1',
host: 'yijiebuyi.com1',
port: 80 }
*/
如何使用 Promise.race
返回一个Promise对象,参数是Promise对象,或Promise数组
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})
案例二:
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// expected output: "two
原理是挺简单的,但是在实际运用中还没有想到什么的使用场景会使用到。
人类身份验证 - SegmentFaultsegmentfault.com 理解和使用Promise.all和Promise.racewww.jianshu.com![36056b47bb4361b8d227b7c1c0465430.png](https://img-blog.csdnimg.cn/img_convert/36056b47bb4361b8d227b7c1c0465430.png)
![36056b47bb4361b8d227b7c1c0465430.png](https://img-blog.csdnimg.cn/img_convert/36056b47bb4361b8d227b7c1c0465430.png)