Promise、async、await 的基本使用及理解
参考
前期知识
函数对象、实例对象
1. 函数对象
函数对象:将函数作为对象使用时,简称为函数对象
// 函数对象
function Person(){
}
Person.age = 0;
console.log(Person.age)
// 输出: 0
//小坑Person.name = Person 无法修改
2.实例对象
实例对象: new 构造函数或类产生的对象,我们称之为实例对象
function Person(name, age){
this.name = name;
this.age = age;
}
const p1 = new Person("小六", 5);
console.log(p1);
// 输出
// Person {name: '小六', age: 5}
在上述的代码中 p1 即为实例对象
回调函数的分类
回调的定义
自己定义的,我们没有调用,最终执行了
分类
-
同步回调函数:
理解:立即在主线程上执行,不会放入回调的队列中
例子: 数组遍历相关的回调函数/ Promise的executor
-
异步的回调函数:
理解: 不会立即执行,会放入回调队列中以后执行
例子:定时器/ ajax 回调 / Promise 的成功、 失败的回调
同步的回调函数
// 同步的回调
let arr = [1, 2, 3, 4, 5, 6, 7]
arr.forEach((i)=>{
console.log(i)
})
console.log("主线程上的代码");
输出
异步回调函数
setTimeout(()=>{
console.log("dd")
}, 1000);
console.log("主线程上的代码");
输出
js 中的error
相关文档: MDN
-
错误的类型
-
Error: 所有错误的父类型
-
ReferenceError: 引用变量不存在
-
TypeError: 数值类型不正确
-
RangeError: 数值不在其所允许范围内 (死循环)
-
SyntaxError: 语法错误
-
-
错误处理
捕获错误: try{} catch(){}
抛出错误; throw error
-
错误对象
message属性: 错误相关信息
stack属性: 记录信息
几种类型错误的示例
// ReferenceError :引用变量不存在
/* console.log(a) */
// TypeError: 数据类型不正确
/* const demo = () => {}
demo()() */
// RangeError: 数据值不在其所允许的范围内
/* const demo = ()=>{demo();}
demo(); */
// SyntaxError: 语法错误
/* console.log(1; */
错误处理
try {
console.log(1);
console.log(a);
console.log(2);
} catch (e) {
console.log("错误了")
}
输出
错误的抛出
function demo(){
const date = Date.now();
if(date % 2 == 0) {
console.log("偶数时间")
} else {
throw new Error("奇数时间")
}
}
try{
demo();
} catch (e) {
console.log("err : ",e)
}
promise 的理解和使用
理解
-
抽象表达:
Promise: 是JS中进行异步编程的新方案 (旧的方案是纯回调)
-
具体表达:
- 从语法上来说: Promise 是一个构造函数
- 从功能上来说: Promise 对象用来封装一个异步的操作并可以获取其结果
使用
简单案例一,Promise 的创建
- new Promise 的时候传入一个回调函数,它是同步的回调,会立即在主线程上执行,它被称为executor函数
- 每一个Promise实例都有三种状态,分别为: a. 初始化(pending) b.成功(fulfilled) c.失败(rejected)
- 每一个Promise实例在刚被new出来的那一刻,状态都是初始化的
const p = new Promise(() => {
console.log("this run");
});
console.log("运行结束")
输出结果
this run
运行结束
结果分析
说明上述的代码是同步执行的,也就是executor
函数是同步的
简单案例二(resolve, reject,状态的指定)
状态只能执行一次
executor 函数会接受到2个参数,它们都是函数,分别用形参:
resolve
,reject
的形式接受声明方式 :
new Promise(executor)
- 调用
resolve
, 会让Promise 实例状态变为: 成功(fulfilled),同时可以指定成功的value- 调用
reject
, 会让Promise实例状态变为: 失败(rejected), 同时可以指定失败的reason
代码
const p = new Promise((resolve, reject) => {
resolve("ok");
})
console.log(p);
const e = new Promise((resolve, reject) => {
reject("错误");
})
console.log(e);
输出
Promise {: ‘ok’}
Promise {: ‘错误’}
简单案例三
代码
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1000);
},1000)
})
p.then(
(value) => console.log("success",value) , // 成功的回调
(reason) => console.log("err",reason) // 失败的回调
);
console.log("end")
输出
end
success 1000
案例四(使用Promise发送请求并获取数据)
代码
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if (xhr.status === 200) resolve(xhr.responseText);
else reject("请求出错");
}
}
xhr.open("GET", "https://api.apiopen.top/api/getHaoKanVideo")
xhr.responseType = "json";
xhr.send()
})
p.then(
(value) => console.log("success1",value) , // 成功的回调
(reason) => console.log("err1",reason) // 失败的回调
)
console.log("end")
输出
成功的输出
错误的输出
理解
其实到这里我们可以理解Promise的作用是帮助我们封装一个异步的函数,并且可以根据异步函数执行的状态,如果成功执行就返回数据,错误执行的话就返回错误的原因(当然这些都可以根据需求而定)。并且我们通过一个
.then
函数可以很方便的获取。
案例五 (使用Promise对案例四进行封装)
通过案例四我们可以通过Promise进行执行一个异步函数,并且获取回调(异步)的结果,不过案例四中的各种参数写的比较固定,因此我们对它进行简单的封装。
代码
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
function myAxios(method,url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if(xhr.readyState === 4) {
if (xhr.status === 200) resolve(xhr.responseText);
else reject("请求出错");
}
}
xhr.open(method, url)
xhr.responseType = "json";
xhr.send()
})
}
myAxios("GET", "https://api.apiopen.top/api/getHaoKanVideo")
.then(res => console.log(res),
err => console.log(err),
)
console.log("end")
分析
输出结果: 与案例四一致
优点: 可以看到我们封装了
myAxios
函数来执行这个异步的任务并且获取了执行结果。封装之后代码的灵活度更高,复用性更高(比如我们要切换不同的url发送请求就不用重新写一个Promise了)。
案例六 (catch回调函数的意义)
代码1
const p = new Promise((resolve, reject) => {
throw new Error("出错!")
reject("错误啦");
})
p.then(res => console.log(res))
.catch(e => console.log("err->",e));
console.log("end")
输出
代码2(等价于代码1)
const p = new Promise((resolve, reject) => {
throw new Error("出错!")
reject("错误啦");
})
p.then(res => console.log(res))
.then(null,e => console.log("err->",e));
console.log("end")
代码3(回调函数中出现异常)
const p = new Promise((resolve, reject) => {
resolve(200);
})
p.then(res => console.log(a))
.catch(e => console.log("err->",e));
console.log("end")
输出
分析
可以从上述的案例中,可以看出
catch
触发的条件是, 当我们传入的函数也就是executor
或者then
回调函数运行出错或者触发reject
的时候,可以通过catch
获取抛出的错误。
案例七 (Promise.all的使用 )
使用方法
Promise.all(promiseArr)
说明
返回一个新的Promise实例,只有当所有的promise都成功才成功(成功的结果以数组的形式返回),只要有一个失败就失败。
代码1(成功执行)
const dataArr = [1,2,3,4,5];
const arr = dataArr.map(item => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(item + 1);
},500)
}))
Promise.all(arr).then(res => console.log(res), err => console.log(err));
console.log("end")
运行结果
代码2(部分失败运行)
const p = Promise.reject(-200);
const promiseArr = [1,2,p];
const arr = promiseArr.map(item => new Promise((resolve, reject) => {
setTimeout(() => {
resolve(item);
},500)
}))
Promise.all(arr).then(res => console.log(res))
.catch(err => console.log("err->", err));
console.log("end")
输出结果
案例八(Promise.race的使用)
使用方法
Promise.race(promiseArr)
说明
返回一个新的Promise实例,成功还是失败以最先出结果的promise为准(也就是说这里race的是时间)。
代码1(成功运行)
const p0 = Promise.resolve(200);
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(-200);
},500);
})
Promise.race([p1,p0]).then(res => console.log("success ->", res))
.catch(err => consol.log("err->", err));
输出
代码2(失败运行)
const p0 = Promise.reject(200);
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(-200);
},500);
})
Promise.race([p1,p0]).then(res => console.log("success ->", res))
.catch(err => console.log("err->", err));
输出
作用
给一个异步任务设置超时时间,例如axios中的
timeout
可以通过race
实现
案例九(_then的链式调用)
说明
如果then所指定的回调返回值是非Promise值a:
那么新Promise状态为:成功,且值为a
如果then所指定的回调返回值是一个Promise实例p:
那么新Promise实例的状态和值,与p一致
如果then所指定的回调抛出异常:
那么新Promise实例状态为rejected, reason 为抛出的异常
代码
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200);
},500);
})
const x = p.then(res => {
console.log("ok1",res)
return new Promise((resolve, reject) => {
resolve(1000);
})
})
.catch(err => {
console.log("err1",err)
return Promise.reject(err)
})
x.then(res => console.log("ok2",res))
.catch(err => console.log("err2",err));
输出
ok1 200
ok2 1000
案例十(中断Promise链式调用)
中断promise链的方式
- 在使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
- 在失败的问题函数中返回一个
pendding
状态的Promise实例
代码
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(200);
},500);
})
p.then(
res => Promise.reject(-200),
err => err)
.then(
res => res,
reason => {
console.log("中断链条-> ",reason)
return new Promise(()=>{})
})
.then(
res => console.log("ok"),
reason => console.log("err"),
)
console.log("end");
输出
案例十一(async、await的使用)
代码
const p0 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(-200);
})
})
const p = async () => {
try {
return await p0;
} catch (error) {
console.log("err->", error)
}
}
p()
输出
err -> -200
分析
async
、await
是配套使用的,其中大致可以理解为async
保证了对应函数的异步。await
是阻塞对应进程并等待结果(与前面代码通过then
调用的效果类似)。
案例十二 (async、await解决链式调用问题)
代码
/**
*
* @param {区分是谁调用的} id
* @param {手动设置回调的成功与否} state
* @returns
*/
function task(id, state=true) {
return new Promise((resolve, reject) => {
setTimeout(() => {
state ? resolve(id) : reject(id);
},1000);
});
}
const p = async() => {
try {
const res1 = await task(1);
console.log(res1);
const res2 = await task(2);
console.log(res2);
const res3 = await task(3,false);
console.log(res3);
const res4 = await task(4);
console.log(res4);
} catch (err) {
console.log("出了点错", err);
}
}
p();
console.log("end");
输出结果
end
1
2
出了点错 3
结果及分析
可以看到数据依次在控制台输出,并且当执行task3的时候由于回调出错导致task4并没有执行。实现了链式调用的问题。
要点:
- 在这里
await
起到的作用可以理解为阻塞当前的线程,等待对应的异步任务结束。try{}catch(){}
起到了错误穿透的作用。p
这个函数依然是异步函数。
总结
要重点理解其中哪些是异步哪些是同步的,以及链式调用的过程。