深入理解Promise、async和await

1. 为什么要Promise?

在JavaScript中,难免会遇到异步访问的场景,比如打开文件,访问数据库等等。如果不做好异步控制,会导致意外结果(比如 返回值还没返回就想调用)因此,一种典型的做法是:

图片A来自阮一峰微博


当时,大家把这种情形称之为回调地狱。基于当时的困境,有人提出了Promise,从英文看得出,这是一个承诺,承诺我将来会干什么(其实干的事情就是回调)。
如何表征这个回调?在promise后面跟上then表示要回调的函数即可实现
即:

图片B来自阮一峰微博


一个promise对象then之后还是promise,因此可以一直then下去,从而实现嵌套回调。之前有了解promise的同学也许会有疑问,那还要catch干嘛?


getData().then(success,fail)表示getData执行后,
如果成功(即resolved ,通过return res或者reslove(res)实现),则回调success函数。
如果失败(即rejectd, 通过 throw res或者reject(res)实现),则回调fail函数。
无论何种,res都将传入到相应的回调函数中。而getData().then(success,null).catch(fail)getData().then(success,fail)的缩略写法,二者是等价的


2. 为什么要async?

从英文字面理解,就是异步的意思,也就是说用async修饰的函数是一个异步函数,用法跟别的函数没区别,仅仅是异步而已。那异步能干什么?

(async function f() {
    console.log('123');
    console.log('321');
    throw '666';
})().then(res=>{
    console.log(res);
},res=>{
    console.log(11);
});

console.log('456');
console.log('6');
console.log('6');
console.log('6');
console.log('6');
console.log('6');
console.log('6');

输出结果

123
321
456
6
6
6
6
6
6
11

我们先定义了一个异步函数,然后执行:进入异步函数后,先打印123,然后抛出一个错误。此时,异步函数交出执行权,排在异步函数后面的console.log得以执行,JS是单线程的,直到最后的6打印完毕,才得以处理异步函数抛出的错误,打印11

继续回答上面的问题,async异步能干什么?就是用来修饰函数,使该函数异步执行,不阻碍后续函数的执行
同时我们注意到,async修饰的函数也带有then catch方法,因此,经async修饰的函数也是一个promise

3. 为什么要await?

从英文字面上理解,await就是等待,等待什么?等待await后面的函数执行完毕,异步函数内部再往下执行。await只能放在async中,且只能修饰promise对象

async function f0() {
    console.log('888');
    return 'ha';
}

async function f1() {
    console.log('777');
    return 'haha';
}

(async function f2() {
    console.log('123');
    await f0();
    await f1();
    console.log('321');
    throw '666';
})().then(res=>{
    console.log(res)
},res=>{
    console.log(11)
});

console.log('456');
console.log('6');
console.log('6');
console.log('6');
console.log('6');
console.log('6');
console.log('6');

输出结果

123
888
456
6
6
6
6
6
6
777
321
11

可能有同学会有疑问,await不是等待么?要等f0执行完才能执行后面的啊。这句话没错,await f1确实在await f0之后。

但是在f0调用结束之前,这都在f2的执行时间内,直到f0运行到 return ha。注意f0是一个promise,在return ha后将调用f0的then方法,注意这是一个异步方法,此时,函数执行权交出,由下面的console.log获得执行权。

因此,等最后一个6打印完毕,再回来执行f0的then方法,但是代码中没有写,则默认执行then(null,null)

因此,await f1再执行。所以,会依次打印777 321 11

为验证上述说法,我们对上述代码做一下改动:

function ff() {
    console.log('000');
}

async function f0() {
    console.log('888');
    return 'ha';
}

async function f1() {
    console.log('777');
    return 'haha';
}

(async function f2() {
    console.log('123');
    ff();
    ff();
    await f0().then(()=>{console.log('awaiting f0')});
    await f1().then(()=>{console.log('awaiting f1')});
    console.log('321');
    throw '666';
})().then(res=>{
    console.log(res)
},res=>{
    console.log(11)
});

console.log('456');
console.log('6');
console.log('6');
console.log('6');
console.log('6');
console.log('6');
console.log('6');

输出结果

123
000
000
888
456
6
6
6
6
6
6
awaiting f0
777
awaiting f1
321
11

123跟888之间存在000 000是因为,此时还在f2的执行权限内,并没有产生then或者catch,因此不会让出执行权。

可是当await f0执行到return ha的时候,将调用f0的then。这将是一个异步调用,f2执行权交出,下面的console.log得以执行。

输出最后一个6,再回来执行f0的then,打印awaiting f0。因此,后面的awaiting f1同理。

故而,用async/await结构来表达之前的图片B

图片C来自阮一峰微博

4. 小结:

  1. promise的诞生是为了简化函数嵌套调用流程,也便于后续维护
  2. async/await定义了异步函数,并在其内部可通过await等待promise对象,阻塞后续的执行

5. 那async/await跟promise有什么区别

一句话,我感觉这是一个东西

创建promise对象有两种方法,
方法1:

async function f1() {
    console.log('777');
    return 'haha';
//使用return 'sth'表示执行成功,
//使用throw 'sth'则表示执行失败,'sth'将传递给then
}

方法2:

var f1 = function () {
    return new Promise((resolve,reject) => {
        setTimeout(()=>{
            resolve(10);
//使用resolve (sth)表示执行成功,
//使用reject (sth)则表示执行失败,'sth'将传递给then
            console.log('f1')
        },1000)
        }

    )
}

为什么方法2要放在函数里作为返回,主要原因是,new promise之后,新建的对象会立即执行。这不是我们想要的情形,因此封装在函数内作为返回值,这样只在调用时才执行了,比如f1()

### JavaScript 中 Promiseasync await 的关系及用法 #### 一、Promise 基础 `Promise` 对象代表了一个异步操作的最终完成(或失败)及其结果值。它有三种状态:待定(pending),已实现(fulfilled)被拒绝(rejected)[^3]。 创建 `Promise` 实例通常是为了封装一些耗时的操作,比如网络请求或者文件读取: ```javascript const eatBreakfast = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve('吃早饭'); }, 1000); }); }; ``` 此代码片段展示了如何通过返回一个新的 `Promise` 来表示一个简单的异步动作——模拟花费一秒时间来“吃早餐”。当这个过程结束时,调用 `resolve()` 方法传递成功的结果给后续链上的回调函数[^4]。 #### 二、使用 then() 处理 Promise 结果 一旦有了 `Promise`,就可以利用 `.then()` 方法注册成功的处理程序以及可选的错误处理器`.catch()`: ```javascript eatBreakfast().then(message => console.log(message)).catch(error => console.error(error)); // 输出 "吃早饭" ``` 这段代码会在 “吃早饭”的承诺兑现之后打印消息;如果过程中发生任何异常,则会被捕获并记录下来[^2]。 #### 三、引入 async/await 提升可读性维护性 为了改善传统基于回调的方式所带来的“回调地狱”,即多层嵌套难以管理的情况,ES8 引入了 `async` `await` 关键词作为更优雅地编写异步逻辑的方法之一。 - **Async Function**: 定义带有 `async` 关键字前缀的函数意味着该函数内部可以安全地使用 `await` 表达式,并且总是隐含地返回一个 `Promise`。 - **Await Expression**: 当在一个 `async` 函数体内遇到 `await` 操作符时,执行流程将会暂停在此处直至对应的 `Promise` 被解决或拒收为止,在这期间不会阻塞其他任务继续运行[^1]。 下面是一个改进版的例子,展示怎样运用这些特性让复杂的异步序列看起来像是顺序执行的一样直观易懂: ```javascript async function dailyRoutine() { try { let breakfastMessage = await eatBreakfast(); console.log(breakfastMessage); // 这里还可以加入更多类似的异步活动... } catch (error) { console.error(`遇到了一个问题: ${error}`); } } dailyRoutine(); ``` 在这个例子中,`dailyRoutine` 是一个异步函数,其中包含了对另一个异步方法 `eatBreakfast` 的调用。由于用了 `await`,所以即使实际发生了延迟,“吃早饭”这条语句也会按照预期顺序被执行完毕后再往下走,从而大大提高了代码的清晰度易于理解的程度。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值