彻底理解Promise和async/await

news/2024/11/23 3:51:02/


Promise

1.异步行为是为了优化因计算量大而时间长的操作.

2.pedding 待定: 表示尚未开始或正在进行中

   fulfilled 解决: 表示已经成功完成

   rejected 拒绝: 表示没有完成

3.从pedding状态切换到fulfilled状态或rejected状态后,状态就不会再改变.而且也不能保证promise比如会脱离待定状态.

因此,无论promise是resolve还是reject,甚至永远处于待定状态,都应该具有恰当的行为

4.执行器函数的两项职责: 初始化promise的异步行为和控制状态的最终转换.

控制promise状态的转换是通过resolve()和reject()来实现的

resolve()会把状态切换为兑现

reject()会把状态切换为拒绝,reject()会抛出错误.

5. 执行器函数是同步执行的, 因为执行器函数是promise的初始化程序

console.log(1);new Promise((resolve,reject) => {console.log(3);
})console.log(2);//打印1, 3, 2

6.无论resolve()和reject()中的哪个被调用,状态转换后都不可撤销. 继续修改状态会默认失败

const p = new Promise((resolve,reject) => {resolve('666')reject('000') // 没有效果
})console.log(p); // 666

7.为了避免promise卡在待定状态,可以添加一个定时退出功能,通过setTimeout设置一个10秒后无论如何都会拒绝promise的回调

如果执行器中的代码在超时之前已经解决或拒绝,那么再次调用reject也会默认失败

8.Promise.resolve()

        通过Promise.resolve()静态方法,可以实例化一个解决的Promise

        使用这个静态方法实际上可以把任何值都转换为一个Promise

const p1 = new Promise((resolve,reject) => resolve())
const p2 = Promise.resolve() // undefined
const p3 = Promise.resolve(3) // 3
const p4 = Promise.resolve(4,5) // 4 多余参数会忽略, 可以使用对象

9.Promise.reject()

实例化一个拒绝的promise并抛出一个异步错误(这个错误不能通过try/catch捕获,只能通过拒绝处理程序捕获)

const p = Promise.reject(4) // promise <rejected> 4

10. Promise的实例方法

10.1.实现Thenable接口

10.2.Promise.prototype.then()

        Promise.prototype.then()是为Promise实例添加处理程序的主要方法.

        then()最多接收两个参数 onResolved处理程序和onRejected处理程序,这两个参数都是可选的分别进入兑现和拒绝状态

let p1 = new Promise((resolve,reject) => setTimeout(resolve,2000))
let p2 = new Promise((resolve,reject) => setTimeout(reject,2000))p1.then(() => onResolved('p1'),() => onRejected('p1'))p2.then(() => onResolved('p2'),() => onRejected('p2'))function onRejected(id){console.log(id,'rejected');
}function onResolved(id){console.log(id,'onResolved');
}

因为promise的只能转换最终状态一次,所以这两个操作一定的互斥的.

传给then()的任何非函数类型的参数都会被静默忽略.如果只提供onRejected参数,要在onResolved位置上传入undefined

p1.then('glsdfosdfisdjif') // 会被忽略,不推荐p1.then(null,() => onRejected('p2')) // 不传onResolved的规范写法

Promise.prototype.then()返回一个新的Promise实例, 会通过Promise.resolve()隐式包装来生成新的Promise

const p3 = Promise.resolve('wei') // 默认返回undefined

10.3.Promise.prototype.catch()

Promise.prototype.catch()是为Promise实例添加拒绝处理程序.

实际上这个方法就是一个语法糖,相当于调用了 Promise.prototype.then(null,onRejected)

返回一个新的Promise实例,会通过Promise.resolve()隐式包装来生成新的Promise

10.4.Promise.prototype.finally()

        Promise.prototype.finally()用于给promise添加onFinally处理程序,在promise转换为解决或者拒绝都会执行, 主要是避免在resolve和rejected中出现冗余代码. 但是没办法知道promise的状态是解决还是拒绝.

返回一个新的Promise实例,不同于then()和catch()方法返回的实例.在大多数情况下它将表现为父Promise的传递.

10.5.非重入Promise方法

当Promise进入落定状态时,与该状态相关的处理程序仅仅会被排期,而非立即执行.(就是被放到一个微任务里去了)

// 例子
const p = Promise.resolve()p.then(() => {console.log('p.then()');
})console.log('同步执行');
// 实际输出 同步执行, p.then()

// 例子

let sy;let p = new Promise((resolve) => {sy = function() {console.log(1);resolve()console.log(2);}
})p.then(() => {console.log(4);
})sy()console.log(3);/* 实际输出: 1,2,3,4 即使Promise的状态发生在添加处理程序之后,处理程序也会等到运行的消息队列让出时,才会执行.
*/ 

10.6.临近处理程序的执行顺序

10.7.传递解决值和拒绝理由

        解决的值和拒绝的理由分别通过resolve()和reject()的第一个参数往后传,直到报错

        Promise.resolve() 和Promise.reject() 在被调用时就会接受解决值和拒绝理由.

10.8.拒绝Promise与拒绝错误处理

        拒绝Promise类似于throw()表达式.它们都代表一种程序状态, 即需要中断或者特殊处理

let p1 = new Promise((resolve,reject) => reject( Error('1')))
let p2 = new Promise((resolve,reject) => { throw Error('2') })
let p3 = Promise.resolve().then(() => { throw Error('3') })
let p4 = Promise.reject(() => Error('4'))console.log(p1);
console.log(p2);
console.log(p3);
console.log(p4);

Promise.resolve().then()的错误最后才出现.

正常情况下,在通过throw()关键字抛出错误时,js运行时的错误处理机制会停止执行抛出错误之后的任何代码(抛出错误之后不解析了)

// 正常情况xxx不会执行
throw Error()
console.log('xxx');
// promise抛出错误时,因为错误实际上是从消息队列中抛出来的,所有不会阻止运行时
Promise.reject(Error('x'))
console.log('xxx'); // 会输出

// 例子:

        let p = new Promise((resolve,reject) => {console.log(1);reject(Error())}).then(() => {console.log(2);}).catch((e) => {console.log(3);}).catch(() => {console.log(4);}).then(() => {console.log(5);})/*实际输出1,3,5 首先打印1, 没毛病, 接下来执行reject(),打印3, catch 函数会隐式的调用Promise.resolve()函数, 因此继续.then()打印5
*/

10.9.Promise连锁与合成

多个Promise组合在一起可以构成强大的代码逻辑.

通过两种方式实现: promise连锁和promise合成,前者是一个promise接一个promise的拼接,后者则是将多个promise组合成一个promise

        //要执行真正的异步任务,让每个执行器都返回一个promise实例let p1 = new Promise((resolve,reject) => {console.log(1);setTimeout(resolve,1000)})p1.then(() => new Promise((resolve,reject) => {console.log(2);setTimeout(resolve,1000)})).then(() => new Promise((resolve,reject) => {console.log(3);setTimeout(resolve,1000)})).then(() => new Promise((resolve,reject) => {console.log(4);setTimeout(resolve,1000)}))// 1 1秒后// 2 2秒后// 3 3秒后// 4 4秒后

   

        Promise的处理程序是按照它们添加的顺序执行的,由于Promise的处理程序是先添加到消息队列, 然后才逐个执行.

将多个Promise实例组合成一个Promise的静态方法. Promise.all()和Promise.race()

Promise.all()方法创建的promise会将一组promise全部解决后,再返回结果.

Promise.all()接收一个可迭代对象,返回一个新Promise

let p1 = Promise.all([Promise.resolve(),Promise.resolve()
])

// 可迭代对象中的元素会通过Promise.resolve()转换为Promise

let p2 = Promise.all([3,4])

// 空的可迭代对象等价于Promise.resolve()

let p2 = Promise.all([])

// 无效的语法

let p2 = Promise.all() // 报错

有一个Promise待定,则合成的Promise也会待定, 有一个Promise拒绝,则合成的Promise也会拒绝.

// 一次拒绝导致最终Promise的拒绝let p1 = Promise.all([Promise.resolve(),Promise.resolve(),Promise.reject()
])

// 如果所有的promise都成功解决,则合成promise的解决值就是所有包含promise解决值的数组(按迭代器顺序)

let p1 = Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.resolve()
])p1.then(res => {console.log(res); [1, 2, undefined]
})

// 多个reject的情况

let p1 = Promise.all([Promise.reject(1),Promise.reject(2),Promise.resolve()
])p1.then(res => {console.log(res); /*1 会将第一个拒绝的理由作为合成promise的拒绝理由. 之后再拒绝的promise的不会影响最终promise的拒绝理由.*/ 
})

Promise.race()方法创建的任意一个 Promise 对象状态变为 fulfilled 或 rejected 时立即返回该 Promise 对象的值或原因。

语法同all()

只要第一个落定的promise,Promise.race()就会包装其解决值或姐拒绝理由并返回新promise

ES6不支持取消Promise和进度通知,一个主要原因就是这样会导致promise连锁和promise合成过渡复杂化.

async await

功能: 让以同步方式写的代码能够异步执行.

async/await解决利用异步结构组织代码的问题.

1. async 关键字用于声明异步函数 可以用在函数声明,函数表达式,箭头函数和方法上

async function foo() {}let bar = async function() {}let baz = async () => {}class Qux {async qux(){}}

使用async关键字可以让函数具有异步特征,但总体上代码仍然是同步求值的.

而在参数或闭包方面,异步函数仍然具有普通函数js函数的正常行为.

例子:

async function foo() {console.log('1');
}foo()console.log(2);
// 1,2

异步函数如果使用return 关键字返回了值(没有return 返回undefined),这个值会被Promise.resolve()包装成一个Promise对象.

async function foo() {console.log('1');// return 666return Promise.resolve(666) // 效果同上
}// 给返回的promise添加一个解决处理程序
foo().then(console.log)// 1, 666

在异步函数中抛出错误会返回拒绝的Promise

async function foo() {console.log('1');throw 3console.log('4'); // 不执行
}// 给返回的promise添加一个解决处理程序
foo().catch(console.log)console.log(2);
// 1, 2, 3

2. 异步函数主要针对不会马上完成的任务,需要一种暂停和恢复执行的能力.

使用await关键字可以暂停异步函数代码的执行,等待Promise的解决.它可以单独使用,也可以在表达式中使用

await命令后面是一个Promise对象. 如果不是,会被转成一个立即resolve的Promise对象.

// 对拒绝promise使用await会释放错误值(将拒绝promise返回)async function foo() {console.log('1');await Promise.reject(3)// return await Promise.resolve(3) // resolve需要return then才能接受, 而reject不需要return catch也可以接收console.log('4'); // 这行代码不会执行
}// 给返回的promise添加一个解决处理程序
foo().then(console.log).catch(console.log)console.log(2);// await Promise.reject(3) 打印输出1,2,3

只要一个await语句后面的Promise变成reject, 那么整个async函数都会中断执行.

// 下面await语句是不会执行的,因为第一个await已经变成了reject
async function foo() {await Promise.reject('出错了')await Promise.resolve('hello') // 不会打印
}

如果希望前一个异步操作失败,也不要中断后面的异步操作.

这时可以将第一个await放在try...catch 这也不管这个异步是否成功,第二个await都会执行

 async function foo() {try {await Promise.reject('出错了') // 会打印} catch (e) {console.log(e);}return await Promise.resolve('hello') // 会打印}foo().then(v => console.log(v))

另一种方式是在await后面的promise对象后添加一个catch方法,处理前面可能出现的错误

    async function foo() {await Promise.reject('出错了').catch (e => console.log(e))//会打印return await Promise.resolve('hello') // 会打印}foo().then(v => console.log(v))

3.await 的限制

await关键字必须在异步函数中使用.

不允许:await出现在箭头函数中

不允许:await出现在同步函数声明中

不允许:await出现在同步函数表达式中

不允许:IIFE使用同步函数表达式或箭头函数

停止和恢复执行

使用await关键字之后的区别其实看上去还要微妙一些.

async function foo() {console.log(await Promise.resolve('foo'));
}async function bar() {console.log(await 'bar');
}async function baz() {console.log('baz');
}foo()
bar()
baz()
// 打印 baz, bar, foo

async/await中真正起作用的是await. async只是一个标识符,毕竟异步函数如果不包含await关键字,基本和普通函数没什么区别

async function foo() {console.log(2);
}console.log(1);
foo()
console.log(3);// 打印 1, 2, 3

要完全理解await关键字,必须知道它并非只是等待一个值可用那么简单.

js运行时在碰到await关键字时, 会记录在哪里暂停执行. 等到await后面的值可用了,js运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行

因此, 即使await后面跟着一个立即可用的值,函数的其余部分也会被异步求值.下面例子:

async function foo() {console.log(2);await nullconsole.log(4);
}console.log(1);
foo()
console.log(3);// 打印 1, 2, 3, 4
/*
运行时的工作过程:
(1) 打印1;
(2) 调用异步函数foo();
(3) (在foo中)打印2;
(4) (在foo中)await关键字暂停执行,为立即可用的值null向消息队列中添加一个任务;
(5) foo()退出;
(6) 打印3;
(7) 同步线程执行完毕;
(8) js运行时从消息队列中取出任务,恢复异步函数执行;
(9) (在foo中)恢复执行,await取得null值(这里并没有用)
(10) (在foo中)打印4;
(11) foo()返回
*/


 

// TC39对await后面是promise的情况如何处理做过一次修改. 修改后await Promise.resolve(8)只会生成一个异步任务

async function foo() {console.log(2);console.log(await Promise.resolve(8));console.log(9);
}async function bar() {console.log(4);console.log(await 6);console.log(7);
}console.log(1);
foo()
console.log(3);
bar()
console.log(5);// 打印 1, 2, 3, 4, 5, 8, 9, 6, 7

使用注意点:

 1.await命令后面的promise对象的运行结果可能是rejected,最好把await放在try...catch代码块中

 2.多个await命令后面的异步操作如果不存在继发关系,最好让他们同时触发

       

// getFoo和getBar是两个独立的异步操作,被写成激发关系,这样比较耗时.可以让它们同时触发let foo = await getFoo()let bar = await getBar()let [foo, bar] = await Promise.all([getFoo,getBar])

async函数的实现原理:

async函数的实现原理就是将Generator函数和自动执行器包装在一个函数里.

面试官: 来说一下你对Promise的理解?

个人对Promise的理解是, promise是一种异步编程的解决方案, 它比传统的回调函数加事件更加合理合强大,

目前除了使用promise的异步操作外,还使用promise在项目中解决回调地狱等问题.

promise是一个对象可以获取异步操作的信息

promise的特点:

         对象不受外界影响,promise一共有三个状态, 分别是进行中,成功和失败,只有异步操作的结果,可以决定是哪一种状态,任何其他的操作都无法改变这个状态.

        一旦状态改变就不会在变,任何时候都可以得到这个结果,promise的状态改变只有两种可能要么成功要么失败

如果要使用promise必须对promise进行实例化, 实例化之后promise内有一个回调函数,这个函数有两个参数,分别是resolve和reject,

当我们的状态发生变化的时候,如果是成功则会通过resolve将成功的结果返回,如果失败通过reject将错误的信息返回出去.

通过.then方法接收成功的结果,通过.catch方法接收失败的结果

promise常用的方法还用promise.all(),race()方法. 主要是将多个实例包装成一个新的实例.

Promise.all()方法创建的promise会将一组promise全部解决后,再返回结果.

Promise.race()方法哪个接口跑的快,先返回哪个

在项目中一般使用promise来对接口进行封装,以及一些异步的操作都会用到promise.校验


http://www.ppmy.cn/news/75954.html

相关文章

制造运营管理 (MOM) 的工作流驱动方法

介绍 “在企业中使用的任何技术的第一条规则是&#xff0c;应用于高效运营的自动化将放大效率。第二个是自动化应用于低效率的操作会放大低效率。” - 比尔盖茨 。 工作流是结构化的活动&#xff0c;主要涉及人与人或人与机器的交互&#xff0c;所有这些活动都旨在在不影响安…

YOLOv5-7.0训练中文标签的数据集

链接&#xff1a;https://pan.baidu.com/s/1KSROxTwyYnNoNxI5Tk13Dg 提取码&#xff1a;8888 以显示楷体为例&#xff08;上面的百度网盘里面有黑体、宋体、楷体ttf文件&#xff09; (1)将metric.py中&#xff1a; 将 sn.set(font_scale1.0 if nc < 50 else 0.8) # for …

<SQL>《SQL命令(含例句)精心整理版(3)》

《SQL命令&#xff08;含例句&#xff09;精心整理版&#xff08;3&#xff09;》 10 联结10.1 联结10.2 高级联结10.3 带聚集函数的联结 11 组合查询13 更新13.1 INSERT13.2 UPDATE13.3 DELETE13.4 truncate 10 联结 10.1 联结 定义说明定义笛卡尔积&#xff08;cartesian p…

公司新招了一个00后软件测试工程师,上来一顿操作给我看呆了...

前段时间公司新来了个同事&#xff0c;听说大学是学的广告专业&#xff0c;因为喜欢IT行业就找了个培训班&#xff0c;后来在一家小公司干了三年&#xff0c;现在跳槽来我们公司。来了之后把现有项目的性能优化了一遍&#xff0c;服务器缩减一半&#xff0c;性能反而提升4倍!给…

面试:浏览器内核的作用及常见浏览器内核

浏览器内核可以分成两部分&#xff1a; 渲染引擎(layout engineer 或者 Rendering Engine)和 JS 引擎。 浏览器的内核的不同对于网页的语法解释会有不同&#xff0c;所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核…

微服务基础-Eureka

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;微服务 &#x1f96d;本文内容&…

程序员的坑你知道多少呢?

程序员不能踩的坑 不要过度优化 - 优化通常是好的&#xff0c;但过度优化可能会导致代码难以维护&#xff0c;也可能会引入新的错误。不要忽略异常 - 异常通常是指程序中出现的错误情况。忽略异常可能会导致程序崩溃&#xff0c;或者在运行时出现未知的错误。不要硬编码敏感信…

物联网调试管理平台

1. 项目介绍 1.1 项目简介 MQTT调试管理平台是一款基于Spring Spring MVC Mybatis开发的一款物联网设备调试管理平台。 其功能主要是对客户MQTT调试页面进行集中管理&#xff08;连接信息、发送信息&#xff09;&#xff0c;系统管理员可在后台添加客户和调试页面&#xf…