JavaScript开发者应懂的33个概念12-Promise,async 与 wait

JavaScript开发者应懂的33个概念12-Promise,async 与 wait

目录

  1. 调用堆栈
  2. 原始类型
  3. 值类型和引用类型
  4. 隐式, 显式, 名义和鸭子类型
  5. == 与 ===, typeof 与 instanceof
  6. this, call, apply 和 bind
  7. 函数作用域, 块级作用域和词法作用域
  8. 闭包
  9. map, reduce, filter 等高阶函数
  10. 表达式和语句
  11. 变量提升
  12. Promise async 与 wait
  13. 立即执行函数, 模块化, 命名空间
  14. 递归
  15. 算法
  16. 数据结构
  17. 消息队列和事件循环
  18. setTimeout, setInterval 和 requestAnimationFrame
  19. 继承, 多态和代码复用
  20. 按位操作符, 类数组对象和类型化数组
  21. DOM 树和渲染过程
  22. new 与构造函数, instanceof 与实例
  23. 原型继承与原型链
  24. Object.create 和 Object.assign
  25. 工厂函数和类
  26. 设计模式
  27. Memoization
  28. 纯函数, 函数副作用和状态变化
  29. 耗性能操作和时间复杂度
  30. JavaScript 引擎
  31. 二进制, 十进制, 十六进制, 科学记数法
  32. 偏函数, 柯里化, Compose 和 Pipe
  33. 代码整洁之道

简介

记录一个学习javascript的过程 ,文章并不是按顺序写的,写完就会更新目录链接 本篇文章目录是参照 @leonardomso 创立,英文版项目地址在这里

前言

本篇文章分为四个部分

  1. promise的介绍和使用
  2. 手写promise
  3. 面试题和项目中使用promise
  4. 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)

image-20231017105720424

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) 结束,并在完成时调用相应的回调函数(传给 thencatch)。

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’**

  1. 步骤一 Promise构造函数会立即执行 按代码顺序执行代码 输出1,2,10, 这时候事件循环里面有异步任务 放到异步队列
  2. 步骤二 异步队列里面又分为宏任务和微任务 然后先执行微任务然后执行宏任务,微任务有: p2.then ,p1().then宏任务有:timeOut1 timeOut2 选执行微任务 输出 5,6
  3. 步骤三 微任务执行完了 开始执行 宏任务:timeOut1 timeOut2,真实的setTimeout入栈顺序是timeOut2>timeOut1 因为promise执行完之后setTimeout2才被加入宏异步任务,所以排在后面,执行timeOut2输出8,执行宏任务的过程中,p3.then微任务进入了队列,宏任务执行完毕会执行微任务,输出:9 之后执行timeOut1,输出:3
  4. 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);

打印的结果

image-20231102150658847

async 函数返回的是一个 Promise 对象

如果async 函数没有返回值呢

image-20231102151025067

它会返回 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')

执行结果

image-20231102153036046

结论:如果await 右边是一个函数,它会立刻执行这个函数,而且只有当这个函数执行结束后(即函数完成)!才会将async剩余任务推入微任务队列

当遇到 await 时,会阻塞函数内部处于它后面的代码(而非整段代码),去执行该函数外部的同步代码;当外部的同步代码执行完毕,再回到该函数执行剩余的代码。并且当 await 执行完毕之后,会优先处理微任务队列的代码

  1. 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()

打印结果

  • 执行第一个函数
  • 执行第三个函数
  • 执行第二个回调函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值