Promise与async/await

Promise与async/await

Promiseasync/await在于解决回调地狱而出现,他们两者有点不同,执行async函数返回的是Promise对象,而async相当于Promisethen

Promise

使用 JavaScript 编写代码会大量的依赖异步计算,计算那些我们现在不需要但将来某时候可能需要的值。所以 ES6 引入了一个新的概念,用于更简单地处理异步任务:Promise

Promise对象是对我们现在尚未得到但将来会得到值的占位符;它是对我们最终能够得知异步计算结果的一种保证。如果我们兑现了我们的承诺,那结果会得到一个值。如果发生了问题,结果则是一个错误,一个为什么不能交付的借口。使用Promise的一个最佳例子是从服务器获取数据:我们要承诺最终会拿到数据,但其实总有可能发生错误。

一个Promise必然处于以下几种状态之一:

  • 待定(pending) :初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled) :意味着操作成功完成。
  • 已拒绝(rejected) :意味着操作失败。

下面是Promise的流程图:

Promise与async/await

Promise的用法

下面是Promise的基础用法,setTimeout模拟的是异步请求,因为setTimeout里调用了resolve,此时的Promise状态为resolved,请求成功之后执行then里面的内容。

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
    });
});
p1.then((res) => {
    console.log('p1 res:' + res);
    console.log(p1);
});

const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
    });
}).then((res) => {
    console.log('p2 res:' + res);
    console.log(p2);
});

const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
        reject('reject');
    });
}).then((res) => {
    console.log('p3 res:' + res);
    console.log(p3);
});

const p4 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
        reject('reject');
    });
}).then((res) => {
    console.log('p4 res:' + res);
    console.log(p4);
}).catch((err) => {
    console.log('p4 err:' + err);
    console.log(p4);
});

Promise与async/await

以上是执行了resolve()Promise,他们都去执行了回调函数then()。接下来我们看看reject()的例子

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject');
        resolve('resolve');
    });
}).then((res) => {
    console.log('p1 res:' + res);
    console.log(p1);
}).catch((err) => {
    console.log('p1 err:' + err);
    console.log(p1);
});

const p2 = new Promise((resolve, reject) => {
    throw('error');
}).then((res) => {
    console.log('p2 res:' + res);
    console.log(p2);
}).catch((err) => {
    console.log('p2 err:' + err);
    console.log(p2);
});

const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        throw('error');
    });
}).then((res) => {
    console.log('p3 res:' + res);
    console.log(p3);
}).catch((err) => {
    console.log('p3 err:' + err);
    console.log(p3);
});
setTimeout(() => {
    console.log(p3);
});

Promise与async/await

通过执行代码发现,p1 p2被执行了catch回调,原因就是Promise主动执行了reject()或者在同步代码里面抛出了异常。但是我们发现p3没有执行catch回调,并且打印出来的Promise p3状态为pending,说明Promise的异步代码里面抛出的异常不会被catch到。

接下来我们再来看看catch调用的调用:

const p1 = new Promise((resolve, reject) => {
    throw('error');
}).then((res) => {
    console.log('p1 res:' + res);
    console.log(p1);
}).catch((err) => {
    console.log('p1 err:' + err);
    console.log(p1);
    return 100;
}).then((res) => {
    console.log('p1 res1:' + res);
    console.log(p1);
});

const p2 = new Promise((resolve, reject) => {
    throw('error');
}).then((res) => {
    console.log('p2 res:' + res);
    console.log(p2);
}).catch((err) => {
    console.log('p2 err:' + err);
    console.log(p2);
    throw('error');
}).then((res) => {
    console.log('p2 res1:' + res);
    console.log(p2);
}).catch((err) => {
    console.log('p2 err1:' + err);
    console.log(p2);
});

Promise与async/await

这里有个小细节,就是在执行catch回调中,没有抛出异常,这时候返回的是一个状态是fulfilledPromise,他会继续执行之后的then回调。

const p1 = new Promise(function(resolve, reject) {
    resolve();
    console.log('p1 resolve');
    throw('error');
    console.log('p1 error');
});

const p2 = new Promise(function(resolve, reject) {
    reject();
    console.log('p2 reject');
    throw('error');
    console.log('p2 error');
});

Promise与async/await

最后是抛出异常,在resolve()reject()后面抛出的错误会被忽略,但是其他在抛出错误代码之上的代码还是会被执行。

下面是Promise的总结:

  • 执行了resolve(),Promise状态会变成fulfilled,即 已完成状态

  • 执行了reject(),Promise状态会变成rejected,即 被拒绝状态

  • Promise只以第一次为准,第一次成功就永久fulfilled,第一次失败就永远状态为rejected

  • Promise的同步代码中有throw的话,就相当于执行了reject()

  • Promise里没有执行resolve()reject()以及throw的话,这个promise的状态也是pending

  • 基于上一条,pending状态下的promise不会执行回调函数then()

  • 执行catch回调中,没有抛出异常,这时候返回的是一个状态是fulfilledPromise

  • Promise的异步代码中有throw的话,Promise无法捕获,只能通过用try...catch包裹Promise解决

  • 基于上一条,pending状态下的promise不会执行回调函数then()

  • resolve()reject()后面抛出的错误会被忽略

async/await

async 函数是使用async关键字声明的函数。async 函数是AsyncFunction构造函数的实例,并且其中允许使用await关键字。asyncawait关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用Promise

下面是async的基础用法:

async function async1 () {
    console.log('async1 000');

    let await1 = await 1000;
    console.log('await1:' + await1);
    console. log('async1 100');

    let await2 = await Promise.resolve(2000);
    console.log('await2:' + await2);
    console. log('async1 200');

    let await3 = await p1;
    console.log('await3:' + await3);
    console. log('async1 300');

    let await4 = await async2();
    console.log('await4:' + await4);
    console. log('async1 400');
}
const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('resolve');
    });
});
async function async2() {
    console. log('async2 000');
    return 400;
}
async1();

Promise与async/await

可以看到上面async1函数里的代码以同步的形式编写,但是具有异步行为。

如果await的异步代码出现异常则可以使用try/catch代码块捕获:

async function async2 () {
    try {
        let await1 = await p2;
    } catch (err){
        console.log('catch async2');  //catch async2
    }
}
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('resolve');
    });
});
async2();
  • async函数一定会返回一个Promise对象

  • 如果一个async函数的返回值看起来不是Promise,那么它将会被隐式地包装在一个Promise

  • async函数可能包含0个或者多个await表达式

  • await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于Promise的异步操作被兑现或被拒绝之后才会恢复进程

  • Promise的解决值会被当作该await表达式的返回值

  • 使用async/await关键字就可以在异步代码中使用普通的try/catch代码块

以一道面试题结束

const async1 = async () => {
    console.log('async1');
    setTimeout(() => {
        console.log('timer1');
    }, 2000);
    await new Promise(resolve => {
        console.log('promise1');
    })
    console.log('async1 end');
    return 'async1 success';
} 
console.log('script start');
async1().then(res => console.log(res));
console.log('script end');
Promise.resolve(1)
    .then(res => {
        console.log(res);
        return 5
    })
    .then(Promise.resolve(3))
    .catch(4)
    .then(res => console.log(res))
setTimeout(() => {
    console.log('timer2');
}, 1000);
点击查看答案
    'script start'
    'async1'
    'promise1'
    'script end'
    1
    5
    'timer2'
    'timer1'

解析:

  • 第一步代码开始,先声明了一个函数async,还未执行,接着继续执行代码,遇到了script start,将其输出到控制台

  • 执行async1,程序跳去async里面,遇到了async1,将其输出到控制台

  • 接着是setTimeout,这时候开启一个计时器,2秒后会将setTimeout的回调放去宏任务执行

  • 遇到关键字awaitawait的是一个Promise,直接执行Promise,将promise1输出到控制台

  • 接下来要留意下,代码里的Promise是没有resolve()reject()的,就是说await还一直在等待Promise

  • 到这一步,async1里面的代码执行完了,接着执行剩下的代码,于是遇到了script end,将其输出到控制台

  • 然后又遇到一个Promise.resolve(1),接着把.then()放去微任务里面等待执行

  • 接下来要回到setTimeout,1秒后会将setTimeout的回调放去宏任务执行

  • 这时候主线程的代码已经执行完了,就开始取出微任务里面的任务执行,即执行.then(),而.then()所在的Promise已经resolve,并且返回了1,所以.thenres1,将其输出到控制台

  • 同理继续执行Promise.resolve(3)所在的.then,又因为这个then没有return,所以将其上级的参数透传下去,故最后执行的.thenres5,将其输出到控制台

最后,让我们一起加油吧!

Promise与async/await

参考资料:

MDN Promise
Promises/A+

原文链接:https://juejin.cn/post/7213285858249195579 作者:周尛先森

(0)
上一篇 2023年3月23日 上午11:16
下一篇 2023年3月23日 上午11:26

相关推荐

发表回复

登录后才能评论