js中的Promise

一、期约

1.1、期约

期约有三种状态:Pending、fulfilled、rejected等,状态是私有的,不能直接检测到,也不能被外部js代码修改。期约状态一旦非Pending了,就不能转换了。

执行器函数:主要负责初始化期约的异步行为和控制状态的最终转换。控制状态主要是依靠resolve和reject函数实现。执行器函数是同步执行的。

js 复制代码
    let p = new Promise((resolve, reject) => { reject() });
    let p1 = Promise.resolve("a");
    let p2 = Promise.reject("c"); // Uncaught (in promise) c
    let p3 = Promise.resolve(p2);
    console.log(p3);// Promise rejected {<rejected>: 'c'}
    let p4 = Promise.resolve(p1);
    console.log(p4);// Promise fulfilled {<fulfilled>: 'a'}
    let p5 = Promise.reject(p1);
    console.log(p5);// Promise rejected {Promise {<fulfilled>: 'a'}}
    console.log(Promise.reject(Promise.resolve(" b")));//Promise rejected {Promise {<fulfilled>: 'b'}}

期约嵌套时:

如果是resolve嵌套resolve那就是幂等逻辑,

如果是reject嵌套resolve那状态就是reject,拒绝期约的理由就是Promise<fulfilled>

如果是resolve嵌套reject,结果还是reject的,翻转不了状态。

1.2、then(resolve,reject)返回新的期约

js 复制代码
    let p = Promise.reject("foo");
    let p1 = p.then(null, () => Error("baz"));
    setTimeout(console.log, 0, p1);// Promise {<fulfilled>: Error: baz

    let p2 = p.then(null, () => {throw 'aaa'});
    setTimeout(console.log, 0, p2);// Promise {<rejected>: 'aaa'}

在这个例子中,p1 是通过 pthen 方法创建的,该方法中使用了一个拒绝处理函数,但是拒绝处理函数返回了一个新的 Error 对象而不是一个 Promise 对象。根据 Promise 的规范,如果拒绝处理函数返回一个非 Promise 值(比如一个普通的对象、字符串、数字等),则会将这个非 Promise 值作为成功的值来解析,并且返回一个状态为已解决(fulfilled)的 Promise 对象,其值就是拒绝处理函数的返回值。

js 复制代码
        let p = Promise.reject("foo");
        let p1 = p.then(null, () => { });
        let p2 = p.then(null, () => undefined);
        let p3 = p.then(null, () => Promise.resolve());
        setTimeout(console.log, 0, p1);
        setTimeout(console.log, 0, p2);
        setTimeout(console.log, 0, p3);
​
        let a = Promise.resolve("foo");
        let a1 = a.then(() => { });
        let a2 = a.then(() => undefined);
        let a3 = a.then(() => Promise.resolve());
        setTimeout(console.log, 0, a1);
        setTimeout(console.log, 0, a2);
        setTimeout(console.log, 0, a3);

以上打印的都是Promise {<fulfilled>: undefined}

Promise.prototype.catch(onReject)就相当于then(null, onReject);

Promise.prototype.finally()则是在转换为解决或者拒绝时执行,多数情况表现为父期约的传递

js 复制代码
        let p = Promise.reject("foo");
        let p1 = p.finally(null, () => { });
        let p2 = p.finally(null, () => undefined);
        let p3 = p.finally(null, () => Promise.resolve());
        setTimeout(console.log, 0, p1);// Promise {<rejected>: 'foo'}
        setTimeout(console.log, 0, p2);// Promise {<rejected>: 'foo'}
        setTimeout(console.log, 0, p3);// Promise {<rejected>: 'foo'}
​
        let a = Promise.resolve("foo");
        let a1 = a.finally(() => { });
        let a2 = a.finally(() => undefined);
        let a3 = a.finally(() => Promise.resolve());
        setTimeout(console.log, 0, a1);// Promise {<fulfilled>: 'foo'}
        setTimeout(console.log, 0, a2);// Promise {<fulfilled>: 'foo'}
        setTimeout(console.log, 0, a3);// Promise {<fulfilled>: 'foo'}
        let a4= a.finally(() => {throw "aa"});
        setTimeout(console.log, 0, a4);// Promise {<rejected>: 'aa'}   

Promise 的非重入特性指的是 Promise 的执行过程是不可中断的,一旦开始执行,就会一直执行到结束,并且期间不会被其他代码打断。具体来说,当一个 Promise 开始执行时,它会一直执行,直到完成(resolve)或拒绝(reject)。在执行过程中,无法中断 Promise 的执行,也无法将其暂停或取消。即使在 Promise 执行过程中发生了异步操作(例如定时器、异步请求等),这些异步操作也无法中断 Promise 的执行,Promise 会一直保持执行状态,直到异步操作完成。

这种特性保证了 Promise 的可靠性和稳定性,确保 Promise 的状态不会被外部因素干扰,从而避免了竞态条件和不确定性的出现。同时,由于 Promise 的执行是非重入的,所以在执行期间无法在 Promise 中插入其他任务或代码片段,保证了 Promise 的原子性和一致性。

在 Java 中,非重入通常指的是一种锁的行为特性。非重入锁是一种锁机制,它不允许同一个线程多次获取同一个锁。换句话说,如果一个线程已经持有了某个锁,再次尝试获取该锁会导致线程阻塞或抛出异常。

在 Java 中,常见的非重入锁包括使用 synchronized 关键字定义的同步块以及 ReentrantLock 类的非公平锁模式。这些锁在同一个线程多次尝试获取同一个锁时,会阻塞该线程或抛出异常,以避免锁的重入。非重入锁的设计有助于避免并发编程中的死锁和饥饿问题,但同时也会增加代码的复杂性和性能开销。

js 复制代码
        let a = Promise.resolve("foo");
        a.then(() => { console.log("a resolved") });
        console.log("test ")

日志打印这里也是test先打印,后续才打印a resolved,因为处理程序会等到运行的消息队列让它出列时才执行。

1.3、传递解决值和拒绝理由

js 复制代码
        let a = Promise.resolve("foo");
        a.then((value) => { console.log(value) }); // foo
​
        let b = Promise.reject("baz");
        b.catch((value) => { console.log(value) });// baz

1.4、期约拒绝

js 复制代码
        let b = Promise.reject("baz");
        b.catch((value) => console.log(value)) // baz
            .then(() => console.log("resolved"));// resolved

为啥catch之后的then只写了resolve方法也能打印,是因为catch方法的返回值是非 Promise 值,那么就会作为成功的值来解析,并且返回一个状态为已解决(fulfilled)的 Promise 对象,后面使用then就可以捕捉了.

1.5、期约连锁与期约合成

then、catch、finally使用。

期约图:有向无环图图,就是二叉树,层序遍历。期约的处理程序是先添加到消息队列,然后才逐个执行,因此构成层序遍历。

js 复制代码
        let A = new Promise((resolve, reject) => {
            console.log("A");
            resolve();
        })
        let B = A.then(() => console.log("B"));
        let C = A.then(() => console.log("C"));
        let D = B.then(() => console.log("D"));
        let E = B.then(() => console.log("E"));
        // A B C D E F 
    //       A
    //      B  C
    //    D  E

1.6、Promise.all和Promise.race

js 复制代码
        let all = Promise.all([
            Promise.resolve(3),
            new Promise((resolve, reject) => {
                console.log("a")
                setTimeout(resolve, 2000);
            })
        ])
        let p = all.then(() => console.log("b"));

a打印之后,间隔2s才打印b

js 复制代码
        let all = Promise.all([
            Promise.reject(3),
            new Promise((resolve, reject) => {
                console.log("a")
                setTimeout(resolve, 2000);
            })
        ])
        let p = all.then(null, () => console.log("b"));

a打印之后,很快打印了b,因为只要有一个reject,那整个就reject,不会执行完后面resolve了。

js 复制代码
      let all = Promise.all([
            new Promise((resolve, reject) => {
                reject();
                console.log("b");
            }), Promise.reject(3)
        ])
        let p = all.then(null, (value) => console.log(value)); // undefined
  1. 在 JavaScript 中,Promise.all()Promise.race() 是两种常用的 Promise 组合方法,它们有以下区别:

Promise.all():

  • 接收一个由多个 Promise 对象组成的可迭代对象(如数组),并返回一个新的 Promise 对象。
  • 当传入的所有 Promise 对象都成功(即状态变为 resolved)时,返回的 Promise 对象的状态变为 resolved,并且将所有 Promise 对象的结果组成的数组作为值传递给成功的回调函数。
  • 如果传入的任何一个 Promise 对象失败(即状态变为 rejected),则返回的 Promise 对象的状态会立即变为 rejected,并且失败的 Promise 对象的结果会作为值传递给失败的回调函数。

Promise.race():

  • 接收一个由多个 Promise 对象组成的可迭代对象(如数组),并返回一个新的 Promise 对象。
  • 当传入的多个 Promise 对象中有任意一个状态发生变化时(无论是成功还是失败),返回的 Promise 对象的状态就会立即变为与该变化一致,并且将第一个状态变化的 Promise 对象的结果作为值传递给对应的回调函数。

简而言之,Promise.all() 用于等待多个 Promise 对象全部完成,然后返回一个包含所有结果的新 Promise 对象;而 Promise.race() 则是用于等待多个 Promise 对象中的任意一个完成,并返回第一个完成的 Promise 对象的结果。

1.7、串行期约合成

js 复制代码
        function add3(x) {
            return x + 3;
        }
        function add5(x) {
            return x + 5;
        }
        function add7(x) {
            return x + 7;
        }
        function add15(x) {
            return add3(add5(add7(x)));
        }

        function add15(x) {
            return Promise.resolve(x).then(add3).then(add5).then(add7);
        }
        add15(0).then((x) => console.log(x));// 15

        function add15(x) {
            return [add3, add5, add7].reduce((promise, fn) => promise.then(fn), Promise.resolve(x));
        }
        add15(0).then((x) => console.log(x));// 15

        function compose(...fns) {
            return (x) => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(x));
        }
        compose(add3,add5,add7)(10).then(console.log); // 25

来看看最后一个方法compose,返回是一个函数,那这个compse调用 compose(add3,add5,add7)返回一个函数,函数的参数传入10结果就是25;

1.8、期约的拓展之取消

Promise规范里期约是不能取消的,但是可以模拟取消。

js 复制代码
        class CancelToken {
            constructor(cancelFn) {
                this.promise = new Promise((resolve, reject) => {
                    console.log("aaa")
                    cancelFn(() => {
                        setTimeout(console.log, 0, "delay cancelled");
                        resolve();
                    });
                });
            }
        }
        const startButton = document.querySelector("#start");
        const cancelButton = document.querySelector("#cancel");
        function cancellable(delay) {
            console.log("set delay");
            return new Promise((resolve, reject) => {
                const id = setTimeout((() => {
                    console.log("delay resolve");
                    resolve();
                }), delay);
                const cancelToken = new CancelToken((callback) => {
                    cancelButton.addEventListener("click", callback);

                })
                cancelToken.promise.then(() => clearTimeout(id));
            })
        }
        startButton.addEventListener("click", () => cancellable(5000));

点击start按钮,创建了一个期约A,期约A初始化时定义了一个5s的定时器,同时创建了一个CancelToken对象,给取消按钮设置了监听,且CancelToken对象创建时会创建一个期约B。一旦取消按钮点击,就会触发B期约解决,就会触发clearTimeout,那么A期约就一直是一个Pending状态。

1.9、期约的拓展之进度通知

期约是不支持进度追踪的,但是可以通过拓展实现。

js 复制代码
        class TrackablePromise extends Promise {
            constructor(executor) {
                const notifyHandlers = [];
                super((resolve, reject) => {
                    return executor(resolve, reject, (status) => {
                        notifyHandlers.map((handler) => handler(status));
                    })
                })
                this.notifyHandlers = notifyHandlers;
            }
            notify(handler) {
                this.notifyHandlers.push(handler);
                return this;
            }

        }
        let p = new TrackablePromise((resolve, reject, notify) => {
            function countDown(x) {
                if (x > 0) {
                    notify(`${20 * x}%remanining`);
                    setTimeout(() => countDown(x - 1), 1000);
                } else {
                    resolve();
                }
            }
            countDown(5);
        })
        p.notify((x) => setTimeout(console.log, 0, "progress", x));
        p.then(() => setTimeout(console.log, 0, "complete"));
  • handler 是一个函数,用于处理任务的进度通知。当执行器函数中调用 notify 方法时,会将该函数作为参数传入,并存储在 notifyHandlers 数组中。当任务的进度发生变化时,会依次调用 notifyHandlers 数组中的每个处理函数,将当前任务的状态传递给它们。
  • status 是描述任务进度的消息。在执行器函数中调用 notify 方法时,会将当前任务的状态作为参数传入,并传递给通知处理函数。在这个例子中,任务的状态是一个表示任务剩余进度的消息字符串,例如 "20% remaining"

因此,handler 是用于处理任务进度通知的函数,而 status 是描述任务进度的消息。

二、异步函数

2.1、异步函数async、await

async就只是一个标志符,真正起作用的是await。javaScript运行时在碰到await关键字时,会记录在哪里暂停执行。等到await右边的值可用了,Javascript运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。

js 复制代码
        async function foo() {
        }
        foo().then((i) => console.log(i)); // undefined
        async function bar() {
            return 1;
        }
        bar().then((i) => console.log(i));//    1

async修饰的函数,返回值会包装成一个fulfilled的期约返回。

js 复制代码
        async function foo1() {
            throw 4;
        }
        foo1().catch((i) => console.log(i)); // 4


        async function foo() {
            Promise.reject(3);
            console.log(5); // 5
        }
        foo().catch((i) => console.log(i)); // Uncaught (in promise) 3

        async function bar() {
            await Promise.reject(4);
            console.log(6); // 这个不执行
        }
        bar().catch((i) => console.log(i));//  4

拒绝的期约可以被await 解包之后,被catch捕获。

bar 函数中,await 关键字会暂停函数的执行,直到 Promise 被解决或拒绝。在这种情况下,Promise.reject(4) 被拒绝了,所以 await 后面的代码不会执行。因此,console.log(6) 不会被执行。

js 复制代码
     async function foo(){
        console.log(await Promise.resolve(9));
     }
     async function bar(){
        console.log(await 8);
     }
     foo();
     bar();

执行顺序打印的是9 8,而不是8 9 。

来看看kt和js中使用async和await的例子:

js 复制代码
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
async function fetchUserData() {
    await delay(1000); // 模拟耗时操作
    return "John Doe";
}
async function main() {
    try {
        console.log("Waiting for user data...");
        const userData = await fetchUserData(); // 等待异步操作的结果
        console.log("User data received:", userData);
    } catch (error) {
        console.error("Error fetching user data:", error);
    }
}
main();
js 复制代码
import kotlinx.coroutines.*

fun main() {
    // 启动一个协程
    GlobalScope.launch {
        val result = async { fetchUserData() } // 启动一个异步任务
        println("Waiting for user data...")
        val userData = result.await() // 等待异步任务的结果
        println("User data received: $userData")
    }
    Thread.sleep(2000) // 主线程等待协程执行
}

suspend fun fetchUserData(): String {
    delay(1000) // 模拟耗时操作
    return "John Doe"
}

怎么感觉kt还复杂一些,用了suspend呢。

2.2、异步函数策略

2.2.1、实现sleep
js 复制代码
        function sleep(delay) {
            return new Promise((resolve, reject) => {
                setTimeout(resolve, delay);
            })
        }
        async function foo() {
            await sleep(1000)
           console.log("job");
        }
        foo();
2.2.2、串行执行期约与利用平行执行
js 复制代码
        async function delay(id) {
            return new Promise((resolve) => {
                setTimeout(() => {
                    console.log(`${id} finished`);
                    resolve();
                }, 1000)
            })
        }
        async function foo(){
            await delay(1);
            await delay(2);
            await delay(3);
            await delay(4);
            console.log("complete");
        }
       foo();
js 复制代码
        async function delay(id) {
            return new Promise((resolve) => {
                setTimeout(() => {
                    console.log(`${id} finished`);
                    resolve();
                }, 1000)
            })
        }
        async function foo(){
            const p1 = delay(1);
            const p2 = delay(2);
            const p3 = delay(3);
            const p4 = delay(4);
            await p1;
            await p2;
            await p3;
            await p4;
            console.log("complete");
        }
       foo();

第一个例子串行执行期约。

第二个例子可以实现平行加速,因为预先创建Promise,在创建p1、p2、p3、p4等已经开始setTimeout了,await就只是取值而已。

相关推荐
崔庆才丨静觅14 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了15 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅15 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅16 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端
爱敲代码的小鱼16 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax