Promise 你真的懂吗

364 阅读3分钟

前言

在探究Promise作用时,先思考下面的代码-->为什么Promise是被设计成同步的,而.then是异步?

//同步
const p = new Promise((resolve,reject) => {

});

// 异步
p.then((res) => {

})

探究

以下demo使用ajaxvite 构建测试环境,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其实是为了防止后续程序都被阻塞的问题
  • 优化地狱嵌套也是用了异步问题同步化这个能力