[ JavaScript ] 对于 Promie 和 async/await 的理解

2021-04-08 14:09:20 浏览数 (1)

对 promise 和 async/await 的理解

promise 是 es6 新增的异步解决方案。虽然, promise 可以解决回掉地狱的问题,但是,如果出现大量的异步请求或者比较复杂的情况下其实也会出现很多的 then ,稍微对 promise 的微任务注册不是理解的特别清晰就会出错。 所以, es7 的 async/await 就是解决这样的一个问题。

Pomise 基本的使用方法

代码语言:txt复制
delayDoAjax(interval){
    return new Promise((resolve, reject) => {
        if (typeof interval !== 'number') return reject(new Error('数据格式有误'));
        setTimeout(() => {
            resolve(`延迟了 ${interval} 后输出`)
        }, interval)
    });
};
// running
console.log(
    this.delayDoAjax(100)
        .then(res => { console.log(res); return this.delayDoAjax(1000) })
        .then(res => console.log(res))
        .catch(err => console.log(err))
);
  // output
  //我延迟了100毫秒后输出的
  //我延迟了1000毫秒后输出的

在执行 resolve 的时候,pomise 的状态就被改变继续向下执行 then ,catch 可以输出错误问题。其中,后面的 then 采用了链式调用的方法。

catch 虽然是放在了最后面,但是,catch 的错误捕获是针对整一个链式调用的,只要其中的一环出现了错误,catch 都能捕获到错误信息。

Promise 微任务

以下面一段代码为例 :

代码语言:txt复制
new Promise((resolve, reject) => {
    resolve('ok')
}).then(res => {
    console.log(`status: ${res}`);
});

new Promise(...).then , 就是同步执行任务,而 then 其中的函数体就是注册到进微任务队列中。

Promise 中的微任务还有 catch 和 finally,就是 Promise 涉及状态变更后需要被执行的回掉才算是微任务。

在多种注册微任务和调用情况下的分析

promise 中 return 新建的 promise
代码语言:txt复制
new Promise((resolve, reject) => {
    console.log('promise-1')
    resolve();
})
    .then(res => {
        console.log("promise-1-then-1");
        return new Promise((resolve) => {
            console.log("promise-2");
            resolve();
        })
            .then(res => {
                console.log("promise-2-then-1");
            })
            .then(res => {
                console.log("promise-2-then-2");
            });
    })
    .then(res => {
        console.log("promise-1-then-2");
    });

这一段代码,里面的 promise 是使用 return 的形式,那么,第一个 promise 的第二个 then 就需要等里面的那个 promise 执行完毕,才会执行 第一个promise 的第二个 then 。

promise 中直接新建 promise
代码语言:txt复制
new Promise((resolve) => {
    console.log('promise-1');
    resolve();
})
    .then(res => {
        console.log('promise-1-then-1');
        new Promise((resolve) => {
            console.log('promise-2')
            resolve();
        })
            .then(res => {
                console.log('promise-2-then-1')
            })
            .then(res => {
                console.log('promise-2-then-2')
            })
            .then(res => {
                console.log('promise-2-then-3')
            })
    })
    .then(res => {
        console.log('promise-1-then-2')
    })
    .then(res => {
        console.log('promise-1-then-3')
    })

由于,外部的第二个 then 的注册,需要等外部的第一个 then 的同步代码执行完毕。所以,在内部的promise 执行完毕,且注册了第一个 then 的时候,外部的同步方法已经执行完毕,这个之后注册了 外部的第二个 then。这时执行了 内部的第二个 then,同时也会注册内部的第三个 then,微任务队列这时会执行 promise-1-then-2 ,所以看起来就是交替的注册和执行,最终的结果如下:

代码语言:txt复制
// promise-1
// promise-1-then-1
// promise-2
// promise-2-then-1
// promise-1-then-2
// promise-2-then-2
// promise-1-then-3
// promise-2-then-3
定义一个 promise 对象
代码语言:txt复制
new Promise((resolve, reject) => {
    console.log('promise-1')
    resolve();
}).then((res) => {
    console.log('promise-1-then-1');
    const response = new Promise((resolve, reject) => {
        resolve();
    });
    response.then(res => {
        console.log('promise-2-then-1');
    });
    response.then(res => {
        console.log('promise-2-then-2');
    })
})
    .then(res => {
        console.log('promise-1-then-2')
    })

由于内部的 then 时同步是通过 response 这个 promise 对象调用的,所以,在这里内部的两个 then 可以说是同时注册 ,当内部的 then 依次执行完毕之后,才会执行外部的 then。

执行的结果如下:

代码语言:txt复制
// promise-1
// promise-1-then-1
// promise-2-then-1
// promise-2-then-2
// promise-1-then-2
定一个 promise 对象,同时 这个 promise 对象里面还有一个 promise
代码语言:txt复制
const response = new Promise((resolve, reject) => {
    console.log("promise-1");
    resolve();
});
response.then(res => {
    new Promise((resolve, reject) => {
        console.log("promise-2");
        resolve();
    })
        .then(res => {
            console.log("promise-2-then-1");
        })
        .then(res => {
            console.log("promise-2-then-2");
        })
        .then(res => {
            console.log("promise-2-then-3");
        });
});
response.then(res => {
    console.log("promise-1-then-2");
});
response.then(res => {
    console.log("promise-1-then-3");
});

首先,第一个 promise 有 3 个 then,都是通过 promise 对象执行为同步事件,执行了第一个then 内部的 pomise 之后,内部的第一个 then 同步事件执行完毕,就会执行第一个 promise 的 2和3的then,执行完毕之后再执行微任务队列的 内部promise 第一个 then ,再依次执行。

执行结果如下:

代码语言:txt复制
// promise-1
// promise-2
// promise-1-then-2
// promise-1-then-3
// promise-2-then-1
// promise-2-then-2
// promise-2-then-3
含有直接创建的 promise 和 return 的 promise
代码语言:txt复制
new Promise((resolve, reject) => {
    console.log("promise-1");
    resolve();
})
    .then((res) => {
        console.log("promise-then-1");
        new Promise((resolve, reject) => {
            resolve();
        })
            .then((res) => {
                console.log("promise-2-then-1");
            })
            .then((res) => {
                console.log("promise-2-then-2");
            });
        return new Promise((resolve, reject) => {
            console.log("promise-3");
            resolve();
        })
            .then((res) => {
                console.log("promise-3-then-1");
            })
            .then((res) => {
                console.log("promise-3-then-2");
            });
    })
    .then((res) => {
        console.log("promise-1-then-2");
    });

这里,首先执行同步函数,一开始就会依次建立好 3个 promise 对象,当第二个 promise 被创建时候,第二个 promise 的第一个 then 会注册进微任务队列,第三个 promise 的第一个 then 也会加入到微任务的队列中,之后再一次执行微任务队列的方法同时又会再注册对应的下一个then和执行,最终的效果看起来也是交替进行,结果如下:

代码语言:txt复制
// promise-1
// promise-1-then-1
// promise-2
// promise-3
// promise-2-then-1
// promise-3-then-1
// promise-2-then-2
// promise-3-then-2
// promise-1-then-2

async/await 的基本使用

代码语言:txt复制
const response = async () => {
    const result = await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('1000');
            console.log('await')
        }, 1000)
    });
    console.log('debugger');
    return result;
}
response().then(res => {
    console.log('res:', result);
})

async 和 await 大多数情况是同时出现的,async 放在 function 前面 是表示这个是一个异步函数,同时,这个函数返回的就是一个promise 对象。

await 意思就是 等待,等待注册的 then 函数体异步返回,在没有收到返回值的情况下,他是不会继续向下执行的。

以上面的例子来说, 输出的顺序一定依次是 await debugger res:1000 。

async/await 之循环遍历执行

代码语言:txt复制
async function ajax(time) {
    return new Promise((reslove, reject) => {
        setTimeout(() => {
            reslove(time);
            console.log(time)
        }, time);
    })
}
const arr = [1000, 3000, 2000];
for (const doc of arr) {
    await ajax(doc);
}

async/await 使用案例分析

依次请求和接收结果的情况

实现需求是 请求 loadData1 拿到参数再请求 loadData2 再拿参数。

例(1):错误写法
代码语言:txt复制
(async () => {
    function ajax(url) {
        console.log("start: "   url);
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(1000);
            }, 1000);
        });
    }
    // 第一次 发送请求
    ajax("loadData1").then((result1) => {
        console.log("result1:", result1);
    });
    // 第二次 发送请求
    const result2 = await ajax("loadData2");
    console.log("result2:", result2);
})();

这样的写法就会有问题,在执行 ajax("loadData1") 的时候,then 是注册进微任务队列之后异步函数处理,这个时候,then 不会执行而是,注册进微任务队列,然后函数直接再向下执行,执行了 ajax("loadData2");,这样子输出的结果就是 :

代码语言:txt复制
// start: loadData1
// start: loadData2
// result1: 1000 
// result2: 1000 

很明显这样就是不对的, 应该两个都是 使用 await ,每一个请求方法的执行 都要等待上一个接口的数据返回,代码如下:

例(1):正确写法
代码语言:txt复制
(async () => {
    function ajax(url) {
        console.log("start: "   url);
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(1000);
            }, 1000);
        });
    }
    // 第一次 请求
    const result1 = await ajax("loadData1");
    console.log("result1:", result1);
    // 第二次 请求
    const result2 = await ajax("loadData2");
    console.log("result2:", result2);
})();

这样 ,ajax("loadData2") 的执行,必须等待 result1 有结果才会 执行 ,代码如下:

代码语言:txt复制
// start: loadData1
// result1: 1000 
// start: loadData2
// result2: 1000 
例(2):错误写法
代码语言:txt复制
// methods
async ajax(name, time){
    return new Promise((reslove, reject) => {
        setTimeout(() => {
            reslove(time);
            console.log(name);
        }, time);
    })
}
// mounted
this.ajax('loadData1', 2000)
this.ajax('loadData2', 1000)

这里的打印结果不是 loadData1 再 loadData2,而是反过来。因为,上下两段 this.ajax 是并列执行的,所以,两个函数的返回值不会依顺序到达,而是谁先回来就输出对应的 name 。

例(2): 正确写法

把上面的例子稍微改下, 在 this.ajax 前面加上 await ,就可以了

代码语言:javascript复制
async ajax(name, time){
    return new Promise((reslove, reject) => {
        setTimeout(() => {
            reslove(time);
            console.log(name);
        }, time);
    })
}
// mounted
async mounted(){
    await this.ajax('loadData1', 2000)
    await this.ajax('loadData2', 1000)
}

欢迎关注 @GavinUI欢迎关注 @GavinUI

0 人点赞