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就只是取值而已。

相关推荐
熊的猫4 分钟前
webpack 核心模块 — loader & plugins
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
速盾cdn11 分钟前
速盾:vue的cdn是干嘛的?
服务器·前端·网络
四喜花露水43 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust2 小时前
css:基础
前端·css
帅帅哥的兜兜2 小时前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
工业甲酰苯胺2 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
yi碗汤园2 小时前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称2 小时前
购物车-多元素组合动画css
前端·css