前言
在探究Promise作用时,先思考下面的代码-->为什么Promise是被设计成同步的,而.then是异步?
//同步
const p = new Promise((resolve,reject) => {
});
// 异步
p.then((res) => {
})
探究
以下demo使用
ajax和vite构建测试环境,data.json中是自定义的json数据,用于模拟请求返回网络数据
demo1
console.log('Promise Demo');
$.ajax({
url: 'http://localhost:3000/data.json',
success (data) {
console.log(handeData(data));
}
})
//这句不会被阻塞
console.log('i am a developer');
function handeData(data) {
return data.map(function(item) {
return item.name;
})
}
思考下上面程序中打印顺序-----> result
Promise Demo
i am a developer
["张三", "李四", "王五"]
- 由于
ajax请求是异步的,所以console.log('i am a developer');不会被阻塞
demo2
console.log('Promise Demo');
var data = $.ajax({
url: 'http://localhost:3000/data.json',
async:false//同步
})
console.log(handeData(data.responseJSON));
//这句会被阻塞
console.log('i am a developer');
思考下上面程序中打印顺序-----> result
Promise Demo
["张三", "李四", "王五"]
i am a developer
- 由于
ajax请求设置了强制同步参数async:false,所以后面的console.log('i am a developer');会被阻塞,只有ajax请求返回数据之后,才能继续执行
demo3
//同步
const p = new Promise((resolve,reject) => {
$.ajax({
url: 'http://localhost:3000/data.json',
success (data) {
resolve(data);
}
})
});
// 异步
p.then((res) => {
console.log(handeData(res));
})
console.log('i am a developer');
思考下上面程序中打印顺序-----> result
Promise Demo
i am a developer
["张三", "李四", "王五"]
- 由于
Promise是同步的,所以会被立即执行,并返回一个实例p - 但是
Promise不会阻塞除p.then以外的下面的程序,故马上会执行console.log('i am a developer'); - 又因为
Promise是个耗时的操作,所以console.log('i am a developer');会被率先打印出来
demo4
function getData() {
return new Promise((resolve, reject) => {
$.ajax({
url: 'http://localhost:3000/data.json',
success (data) {
resolve(data);
}
})
})
}
async function doSomething() {
const data = await getData();
console.log(handeData(data));
}
doSomething()
console.log('i am a developer');
function handeData(data) {
return data.map(function(item) {
return item.name;
})
}
思考下上面程序中打印顺序-----> result
Promise Demo
i am a developer
["张三", "李四", "王五"]
- demo4其实是demo3的优化改造
- 将
Promise封装成一个getData()函数,使用await关键字去修饰这个函数方法,使其外侧的doSomething()函数是一个异步的函数;这样doSomething()这个函数就不再阻塞后续程序console.log('i am a developer');的执行
demo5 关于地狱嵌套
console.log('Promise Demo');
function doSomethingWithoutPromise() {
$.ajax({
url: 'http://localhost:3000/data0.json',
success (data0) {
console.log(data0);
//下面data1.json 请请求基于data0.json返回回来的数据data0
$.ajax({
url: 'http://localhost:3000/data1.json',
data: data0,
success (data1) {
console.log(data1);
//下面data2.json 请请求基于data1.json返回回来的数据data1
$.ajax({
url: 'http://localhost:3000/data2.json',
data: data1,
success (data2) {
console.log(data2);
}
})
}
})
}
})
}
doSomethingWithoutPromise();
console.log('i am a developer');
打印结果
Promise Demo
i am a developer
{data0: "data0"}
{data1: "data1"}
{data2: "data2"}
- 可以看到
doSomethingWithoutPromise();内部是异步的,所以并没有阻塞console.log('i am a developer');, - 但是在
doSomethingWithoutPromise();方法中 由于三个ajax请求中,每个请求都是基于上一个请求的返回的结果作为参数,才能继续下一步请求。 - 这种三个ajax请求嵌套对于初学者来说能很直观的知道每个请求之间的关系(这其实是优点)。
- 一旦嵌套多了(如内部还有
ajax请求),就不再直观了,代码维护也会变得异常麻烦。并且如果一旦每个请求之间增加了一些复杂的数据处理,那么直观的阅读感又会差很多,并且很容易出错。
demo6 使用Promise优化地狱嵌套
console.log('Promise Demo');
function getData0() {
return new Promise((resolve, reject) => {
$.ajax({
url: 'http://localhost:3000/data0.json',
success (data) {
console.log(data);
resolve(data);
}
})
})
}
function getData1(pram) {
return new Promise((resolve, reject) => {
$.ajax({
url: 'http://localhost:3000/data1.json',
data: pram,
success (data) {
console.log(data);
resolve(data);
}
})
})
}
function getData2(pram) {
return new Promise((resolve, reject) => {
$.ajax({
url: 'http://localhost:3000/data2.json',
data: pram,
success (data) {
console.log(data);
resolve(data); ;
}
})
})
}
async function doSomethingWithPromise() {
const data0result = await getData0();
//你可以在这里处理 data0result 的一些复杂运算
//...
const data1result = await getData1(data0result);
//你可以在这里处理 data1result 的一些复杂运算
//...
const data2result = await getData2(data1result);
}
doSomethingWithPromise()
console.log('i am a developer');
打印结果
Promise Demo
i am a developer
{data0: "data0"}
{data1: "data1"}
{data2: "data2"}
- 和dome5一样,打印顺序没有变
- 但是
doSomethingWithPromise()函数中的结构别分散到了三个Promise封装过的函数中,结构更加清晰 - 每个
Promise封装过的函数中(getData0()、getData1()、getData2())相互独立,可维护性大大增加 - 如果还有
getData3()、getData4()...等函数需要嵌套,也可以很容易的加入与维护
总结
- demo1说明: 异步耗时操作不应该阻塞主线程
- demo2说明: 强制耗时操作成为同步操作,会阻塞后续整个程序的执行
- demo3说明:
Promise是同步的,但是它能马上返回一个实例,不会阻塞下面的程序(不包含.then) - demo4说明: 将Promise封装一个耗时函数(耗时操作),在调用时使用
await关键字去修饰这个耗时函数,就可以变成同步操作;而外层函数加上async关键字后,就会形成外部异步,内部同步 - demo5说明: 地狱嵌套的问题: 1.代码不直观 2.维护性差
- demo6说明: 使用demo4的方法可以优化地狱嵌套带来的问题
回到文章开头提出的问题:为什么Promise是被设计成同步的,而.then是异步?
答: 不阻塞和Promise无关的任何程序
Promise的同步和.then的异步能力,成就了异步问题同步化解决方案.Promise+.then其实是为了防止后续程序都被阻塞的问题- 优化地狱嵌套也是用了
异步问题同步化这个能力