JavaScript开发者应懂的33个概念12-Promise,async 与 wait
JavaScript开发者应懂的33个概念12-Promise,async 与 wait
目录
- 调用堆栈
- 原始类型
- 值类型和引用类型
- 隐式, 显式, 名义和鸭子类型
- == 与 ===, typeof 与 instanceof
- this, call, apply 和 bind
- 函数作用域, 块级作用域和词法作用域
- 闭包
- map, reduce, filter 等高阶函数
- 表达式和语句
- 变量提升
- Promise async 与 wait
- 立即执行函数, 模块化, 命名空间
- 递归
- 算法
- 数据结构
- 消息队列和事件循环
- setTimeout, setInterval 和 requestAnimationFrame
- 继承, 多态和代码复用
- 按位操作符, 类数组对象和类型化数组
- DOM 树和渲染过程
- new 与构造函数, instanceof 与实例
- 原型继承与原型链
- Object.create 和 Object.assign
- 工厂函数和类
- 设计模式
- Memoization
- 纯函数, 函数副作用和状态变化
- 耗性能操作和时间复杂度
- JavaScript 引擎
- 二进制, 十进制, 十六进制, 科学记数法
- 偏函数, 柯里化, Compose 和 Pipe
- 代码整洁之道
简介
记录一个学习javascript的过程 ,文章并不是按顺序写的,写完就会更新目录链接 本篇文章目录是参照 @leonardomso 创立,英文版项目地址在这里
前言
本篇文章分为四个部分
- promise的介绍和使用
- 手写promise
- 面试题和项目中使用promise
- async 与 wait
1.promise的介绍和使用
1.1介绍
Promise
对象表示异步操作最终的完成(或失败)以及其结果值, 一个 Promise
是一个代理,它代表一个在创建 promise 时不一定已知的值。它允许你将处理程序与异步操作的最终成功值或失败原因关联起来。这使得异步方法可以像同步方法一样返回值:异步方法不会立即返回最终值,而是返回一个 promise,以便在将来的某个时间点提供该值。
一个 Promise
必然处于以下几种状态之一:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled):意味着操作成功完成。
- 已拒绝(rejected):意味着操作失败。
一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态 immutable)
看一个例子
let p1 = new Promise((resolve, reject) => {
resolve('成功')
reject('失败')
})
console.log('p1', p1)
let p2 = new Promise((resolve, reject) => {
reject('失败')
resolve('成功')
})
console.log('p2', p2)
Promise只以第一次为准
,第一次成功就永久
为fulfilled
,第一次失败就永远状态为rejected
能再迁移至其他任何状态
1.2.Promise解决的问题
在 Promise 出现以前,我们处理多个异步网络请求,大概是这样
请求1(function(请求结果1){
请求2(function(请求结果2){
请求3(function(请求结果3){
请求4(function(请求结果4){
请求5(function(请求结果5){
请求6(function(请求结果3){
...
})
})
})
})
})
})
有了promise
new Promise(请求1)
.then(请求2(请求结果1))
.then(请求3(请求结果2))
.then(请求4(请求结果3))
.then(请求5(请求结果4))
.catch(处理异常(异常信息))
总结如下
-
一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)
-
一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换
-
promise必须实现
then
方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致 -
then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。
-
Promise是一种异步编程的解决方案,它可以解决异步回调地狱的问题,使得异步编程更加清晰和简洁。在传统的异步编程中,如果存在多个异步任务,每个异步任务都需要在上一个异步任务完成之后才能进行,这样就会形成嵌套的回调函数,造成代码难以维护和阅读。而Promise可以将多个异步任务串行或者并行执行,并且可以对异步任务的成功或失败状态进行统一的处理。
在使用Promise时,可以通过链式调用的方式将多个异步任务串联起来,避免了嵌套回调函数的问题。同时,Promise还提供了统一的catch方法,可以捕获所有Promise链中的错误,更加方便地处理异常情况。
因此,Promise解决了异步编程的可读性和可维护性问题,提升了代码的质量和开发的效率
1.4 Promise的使用
基本用法
let p = new Promise((resolve, reject) => {
//做一些异步操作
setTimeout(() => {
console.log('执行完成');
resolve('我是成功!!');
}, 2000);
});
then的用法
const p1 = new Promise((resolve, reject) => {
resolve('成功1')
}).then(res => {
console.log(res)
}).catch(error => {
console.log(error);
})
//输入成功1
//也可以简写成下面这样
const p1 = new Promise((resolve, reject) => {
resolve('成功1')
}).then(res => console.log(res), err => console.log(err))
then有定时器情况
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功1')
}, 2000)
}).then(res => {
console.log(res)
}).catch(error => {
console.log(error);
})
//2秒后输出成功1
catch
const p1 = new Promise((resolve, reject) => {
reject('失败1')
}).then(res => {
console.log(res)
}).catch(error => {
console.log(error);
})
//输出失败1
上面的例子可以看出
被创建的 promise 最终会以被解决状态 (fulfilled)
或 被拒绝状态 (rejected)
结束,并在完成时调用相应的回调函数(传给 then 和 catch)。
then.链式调用 下一次then执行受上一次then返回值的影响
then方法本身会返回一个新的Promise对象
const p1 = new Promise((resolve, reject) => {
resolve(100)
}).then(res => {
return res+1
}).then(res=>{
console.log(res);
})
//输出101
then是微任务
const p1 = new Promise((resolve, reject) => {
resolve('1')
}).then(res => {
console.log(res)
}).catch(error => {
console.log(error);
})
console.log(2)
//输出顺序 2 , 1
promise的方法
方法名 | 说明 |
---|---|
Promise.all([Promise1, Promise2]) | 成功都成功,失败一个返回失败那个 非Promise项成功返回 |
Promise.any([Promise1, Promise2]) | 与all相反 ,都失败,则报错 成功一个返回成功那个 非Promise项,则此项当做成功 |
Promise.allSettled([Promise1, Promise2]) | 把每一个Promise的结果,集合成数组,返回 |
Promise.race([Promise1, Promise2]) | 哪个Promise最快得到结果,就返回那个结果,无论成功失败 |
2.手写promise
1.1结构
原生的promise这样写
const p1 = new Promise((resolve, reject)=>{})
可以看出原生的promise 可以被new 可以自己执行resolve, reject方法
所以我们这样写 定义一个Mypromise 用 constructor 执行 resolve, reject方法
class Mypromise{
constructor(func) {
func(this.resolve,this.reject())
}
resolve(){}
reject(){}
}
// constructor是JavaScript中的一个特殊方法,它是在创建一个新的对象实例时自动调用的。constructor方法会在一个类被实例化时执行,用于初始化该实例的属性值。
promise的三个个状态 pending(待定),fulfilled(成功)rejected(失败) 默认状态是pending,然后执行的时候状态变成fulfilled或rejected,fulfilled或rejected peomise的 fulfilled或rejected 都可以传入一个参数的
我们可以这样写
class Mypromise {
static PENDING = '待定';
static FULFILLED = "成功";
static REJECTED = "拒绝"
constructor(func) {
this.status = Mypromise.PENDING; //默认状态是pending
this.result = null;
func(this.resolve, this.reject())
}
resolve(result) {
if (this.status === Mypromise.PENDING) {
this.status = Mypromise.FULFILLED
this.result = result
}
}
reject(result) {
if (this.status === Mypromise.PENDING) {
this.status = Mypromise.REJECTED
this.result = result
}
}
}
敲黑板了,凡是被static修饰的属性和方法都是静态方法和属性,只能被类名调用,不能被实例化对象调用.同时也不能被子类继承,换句话说它属于当前这个类的.
then方法
class Mypromise {
static PENDING = '待定';
static FULFILLED = "成功";
static REJECTED = "拒绝"
constructor(func) {
this.status = Mypromise.PENDING; //默认状态是pending
this.result = null;
func(this.resolve.bind(this), this.reject.bind(this))
//bind 给实例的resolve方法绑定这个this为当前的实例对象,并且执行resolve方法
}
resolve(result) {
if (this.status === Mypromise.PENDING) {
this.status = Mypromise.FULFILLED
this.result = result
}
}
reject(result) {
if (this.status === Mypromise.PENDING) {
this.status = Mypromise.REJECTED
this.result = result
}
}
then(onFULFILLED,onREJECTED){
if (this.status === Mypromise.FULFILLED) {
onFULFILLED(this.result)
}
if (this.status === Mypromise.REJECTED) {
onREJECTED(this.result)
}
}
}
let p1 = new Mypromise((resolve,reject)=>{
resolve("成功")
})
p1.then(
result => {
console.log(result)
},
result => {
console.log(result.message)
}
)
//输出成功
用try catch 抛出异常 ,判断函数是否为空
class Mypromise {
static PENDING = '待定';
static FULFILLED = "成功";
static REJECTED = "拒绝"
constructor(func) {
this.status = Mypromise.PENDING; //默认状态是pending
this.result = null;
try{
func(this.resolve.bind(this), this.reject.bind(this))
}catch (error){
this.reject(error)
}
//bind 给实例的resolve方法绑定这个this为当前的实例对象,并且执行resolve方法
}
resolve(result) {
if (this.status === Mypromise.PENDING) {
this.status = Mypromise.FULFILLED
this.result = result
}
}
reject(result) {
if (this.status === Mypromise.PENDING) {
this.status = Mypromise.REJECTED
this.result = result
}
}
then(onFULFILLED,onREJECTED){
onFULFILLED = typeof onFULFILLED === "function" ? onFULFILLED :() =>{};
onREJECTED = typeof onREJECTED === "function" ? onREJECTED :() =>{};
if (this.status === Mypromise.FULFILLED) {
onFULFILLED(this.result)
}
if (this.status === Mypromise.REJECTED) {
onREJECTED(this.result)
}
}
}
let p1 = new Mypromise((resolve,reject)=>{
resolve("成功")
})
p1.then(
result => {
console.log(result)
},
result => {
console.log(result.message)
}
)
3.面试题
题目一
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
reject('error');
})
promise.then(() => {
console.log(3);
}).catch(e => console.log(e))
console.log(4);
规则一,promise
构造函数的代码会立即执行,then
或者reject
里面的代码会放入异步微任务队列,在宏任务结束后会立即执行。规则二:promise
的状态一旦变更为成功或者失败,则不会再次改变,所以执行结果为:1,2,4,3。而catch
里面的函数不会再执行。
题目二
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('once')
resolve('success')
}, 1000)
})
promise.then((res) => {
console.log(res)
})
promise.then((res) => {
console.log(res)
})
promise
的构造函数只会执行一次,而then
方法可以多次调用,但是第二次是直接返回结果,不会有异步等待的时间,所以执行结果是: 过一秒打印:once,success,success
。
题目三
const p1 = () => (new Promise((resolve, reject) => {
console.log(1);
let p2 = new Promise((resolve, reject) => {
console.log(2);
const timeOut1 = setTimeout(() => {
console.log(3);
resolve(4);
}, 0)
resolve(5);
});
resolve(6);
p2.then((arg) => {
console.log(arg);
});
}));
const timeOut2 = setTimeout(() => {
console.log(8);
const p3 = new Promise(reject => {
reject(9);
}).then(res => {
console.log(res)
})
}, 0)
p1().then((arg) => {
console.log(arg);
});
console.log(10);
从代码执行顺序的角度来看,程序最开始是按代码顺序执行代码的,遇到同步任务,立刻执行;遇到异步任务,则只是调用异步函数发起异步请求。此时,异步任务开始执行异步操作,执行完成后到消息队列中排队。程序按照代码顺序执行完毕后,查询消息队列中是否有等待的消息。如果有,则按照次序从消息队列中把消息放到执行栈中执行。执行完毕后,再从消息队列中获取消息,再执行,不断重复。
事件循环:javascript
的执行规则里面有个**事件循环EventLoot*p的规则,在事件循环中,异步事件会放到异步队列里面,但是异步队列里面又分为宏任务和微任务,浏览器端的宏任务一般有:script标签,setTimeout,setInterval,setImmediate,requestAnimationFrame
。微任务有:MutationObserver,Promise.then catch finally
。宏任务会阻塞浏览器的渲染进程,微任务会在宏任务结束后立即执行,在渲染之前。
上题的结果为**:'1,2,10,5,6,8,9,3'**
- 步骤一
Promise
构造函数会立即执行 按代码顺序执行代码输出1,2,10
, 这时候事件循环里面有异步任务 放到异步队列 - 步骤二 异步队列里面又分为宏任务和微任务 然后先执行微任务然后执行宏任务,
微任务有: p2.then ,p1().then
宏任务有:timeOut1 timeOut2 选执行微任务输出 5,6
- 步骤三 微任务执行完了 开始执行 宏任务:timeOut1 timeOut2,真实的setTimeout入栈顺序是timeOut2>timeOut1 因为promise执行完之后setTimeout2才被加入宏异步任务,所以排在后面,执行timeOut2
输出8
,执行宏任务的过程中,p3.then微任务进入了队列,宏任务执行完毕会执行微任务,输出:9
之后执行timeOut1,输出:3
promise
的构造函数只会执行一次 4不会打印出来
4.async 与 wait
async/await
通过同步的方式执行异步任务- async声明该函数是异步的,且该函数会返回一个promise。
- await必须放在async函数中使用
1.async 函数是怎么处理它的返回值的?看下面的例子
async function test() {
return 'hello async';
}
let result = test();
console.log(result);
打印的结果
async 函数返回的是一个 Promise 对象
如果async 函数没有返回值呢
它会返回 Promise.resolve(undefined)。
2.await 到底在等啥?看下面例子
function getSomething(){
return "something";
}
async function testAsync(){
return Promise.resolve('hello async');
}
async function test(){
let v1 = await getSomething();
let v2 = await testAsync();
console.log(v1,v2);
}
test();
console.log('我执行了');
//执行结果为:
//我执行了
//something,hello async
结论
1.如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西
2.如果它等到的是一个 Promise 对象,await 就忙起来了,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
3.await+函数
function fn() {
console.log('fn start')
console.log('fn end')
}
async function run() {
console.log('start 1')
const res = await fn()
console.log("111",res)
console.log('end')
}
run()
console.log('3')
执行结果
结论:如果await 右边是一个函数
,它会立刻执行这个函数,而且只有当这个函数执行结束后(即函数完成)!才会将async剩余任务推入微任务队列
当遇到 await
时,会阻塞函数内部处于它后面的代码(而非整段代码),去执行该函数外部的同步代码;当外部的同步代码执行完毕,再回到该函数执行剩余的代码。并且当 await
执行完毕之后,会优先处理微任务队列的代码
-
await加 async 作用
看一个例子
function funOne() { return ("执行第一个函数"); } function funTwo() { $.ajax({ url:'./data.json', success: (res) => { return("执行第二个回调函数"); } }) } function funThree() { return ("执行第三个函数"); } function run() { console.log(funOne()); console.log(funTwo()); console.log(funThree()); } run()
打印结果
- 执行第一个函数
- 执行第三个函数
- 执行第二个回调函数
我们加入一个异步的方法
function funOne() {
return ("执行第一个函数");
}
function funTwo() {
return new Promise((resolve,reject) => {
resolve("执行第二个回调函数");
}).then(res => {
console.log(res)
})
}
function funThree() {
return ("执行第三个函数");
}
function run() {
console.log(funOne());
console.log(funTwo());
console.log(funThree());
}
run()
打印结果
- 执行第一个函数
- 执行第三个个回调函数
- 执行第二个函数
用await加 async 改造一下
function funOne() {
return ("执行第一个函数");
}
function funTwo() {
return new Promise((resolve,reject) => {
resolve("执行第二个回调函数");
}).then(res => {
console.log("1111",res)
})
}
function funThree() {
return ("执行第三个函数");
}
async function run() {
console.log(funOne());
console.log(await funTwo());
console.log(funThree());
}
run()
打印结果
- 执行第一个函数
- 执行第三个函数
- 执行第二个回调函数
转载自:https://juejin.cn/post/7296752240631693347