Promise状态控制权外置

182 阅读4分钟
  • 1、promise状态基础知识点:

    • 我们知道一个 promise 是有三个状态的,pending、fulfilled、rejected
    • 当一个promise 被创建的时候,它的状态被初始化成了 pending 状态
    • 同时 promise 提供了两个方法:resolve、reject 来修改这个promise状态;调用resolve方法将Promise的状态从pending变为fulfilled,而reject方法则将状态从pending变为rejected
  • 2、现行promise常用使用态

    • 现行的 promise 中,我们大多将 resolve、reject 放在 promise 内部来处理 promise 的状态
        const promise = new Promise((resolve, reject) => {
            // 异步操作
            if (/* 操作成功 */) {
                resolve(value); // 将 Promise 状态改为 fulfilled,并传递结果值
            } else {
                reject(error); // 将 Promise 状态改为 rejected,并传递错误信息
            }
        }).catch(err=>{console.log(err)})
    
  • 3、现行promise常用使用态的一些使用思考

    • 现行的使用常态,绝大部分场景都是能正常覆盖的
    • 但是有时间我们希望,能在 promise 外部,自由控制调取 resolve、reject,来控制 promise 的状态变化,该怎么办呢?
  • 4、我们的期许

    • 在某些情况下,我们希望在创建 Promise 时不立即执行某些逻辑,而是在稍后的某个时间点控制何时执行和如何执行。这时,可以通过将执行逻辑封装在另一个函数中,然后在需要的时候调用这个函数来控制 resolve 和 reject 的调用。
    • 将 Promise 的 resolve 和 reject 控制权从 Promise 构造函数中分离出来的编程模式。它创建了一个包含 Promise 及其解决方法的对象,允许你在 Promise 外部控制其状态
  • 5、现有解法

    • 我们可以封装,拆解出 promise deferred 【deferred译:延期 】 对象

    • deferred 对象通常包含三个部分:

      • promise - 标准的 Promise 对象
      • resolve - 用于解决 Promise 的函数
      • reject - 用于拒绝 Promise 的函数
    • 封装输出:

        function getDeferredPromise() {
            let resolve, reject;
            const promise = new Promise((res, rej) => {
                resolve = res;
                reject = rej;
            });
            
            return { promise, resolve, reject };
        }
    
    • 与常态 Promise 的对比


      特性标准 PromiseDeferred 模式的 Promise
      控制权在构造函数内部外部可控制
      解决时机必须在构造函数中决定可在任何时间、任何地点决定
      适用场景已知的异步操作需要外部控制的异步操作
  • 6、常用应用场景

    • 1. 用户交互控制

        function createConfirmationDialog() {
            const {promise, resolve, reject} = getDeferredPromise();
            
            document.getElementById('confirm-btn').onclick = () => {
                resolve(true);
            };
            
            document.getElementById('cancel-btn').onclick = () => {
                resolve(false);
            };
            
            return promise;
        }
    
        // 使用
        createConfirmationDialog().then(confirmed => {
        if (confirmed) {
                console.log('用户确认');
            } else {
                console.log('用户取消');
            }
        });
    
    • 2. 文件加载

        function loadImg(){
            const {promise, resolve, reject} = getDeferredPromise();
            
            const Img = new Image()
            Img.src='xxx'
            Img.onload = function() {
                console.log('图片加载完成了,可以写业务了')
                resolve("图片加载成功了~~")
            };
            Img.onerror = function() {
                console.log('图片加载完成了,可以写业务了')
                reject("图片加载失败了~~")
            };
    
            return promise
        }
    
    
        loadImg().then(res=>{console.log("图片加载成功了~~")}).catch(err=> console.log(err, "图片加载失败了~~"))
    
    • 3. 异步操作队列

        class AsyncQueue {
            constructor() {
                this.queue = [];
                this.current = null;
            }
            
            add(task) {
                const deferredPromise = getDeferredPromise();
                this.queue.push({ task, deferredPromise });
                if (!this.current) this.processNext();
                return deferredPromise.promise;
            }
            
            processNext() {
                if (this.queue.length === 0) {
                this.current = null;
                return;
                }
                
                const { task, deferredPromise } = this.queue.shift();
                this.current = task()
                .then(result => deferredPromise.resolve(result))
                .catch(error => deferredPromise.reject(error))
                .finally(() => this.processNext());
            }
        }
    
    • 4. API 请求超时控制

        function fetchWithTimeout(url, timeout) {
        const {promise, resolve, reject} = getDeferredPromise();
        
        fetch(url)
            .then(response => resolve(response))
            .catch(error => reject(error));
        
        setTimeout(() => {
            reject(new Error('request timeout'));
        }, timeout);
        
            return promise;
        }
    
  • 7、 现代 JavaScript 中的替代方案

    虽然 Deferred 模式的 promise 很有用,但现代 JavaScript 提供了一些替代方案:

    1. AbortController - 用于取消 fetch 请求
        const controller = new AbortController();
        fetch(url, { signal: controller.signal });
        controller.abort(); // 取消请求
    
    1. async/await - 简化异步流程控制
        async function handleUserAction() {
            try {
            const result = await someAsyncOperation();
            // 处理结果
            } catch (error) {
            // 处理错误
            }
        }
    
    1. EventTarget - 用于事件驱动的场景
        const eventTarget = new EventTarget();
        eventTarget.addEventListener('done', () => { ... });
        eventTarget.dispatchEvent(new Event('done'));
    
  • 8、deferred模式的 promise 状态控制权外置 注意事项

    • 内存泄漏 - 长时间持有未解决的 Deferred 可能导致内存泄漏
    • 状态单一性 - Promise 只能被解决或拒绝一次
    • 错误处理 - 确保所有 reject 都被正确处理
    • 竞态条件 - 在多线程环境(如 Web Worker)中要特别小心
  • 9、总结

    deferred模式的 promise 状态控制权外置是强大,特别适合需要从外部控制 Promise 解决时机的场景。虽然现代 JavaScript 提供了更多内置解决方案,但在某些情况下,该模式仍然是处理复杂异步流程的有效方法。使用时应当权衡其灵活性与潜在复杂度,选择最适合特定场景的方案。

  • 10、拓展篇外

    新版的es 提案中 新增的 promise 静态方法 Promise.withResolvers() 方法也是和上述主题有一样的效果,感兴趣的大佬,可以自行mdn。

    同时感谢评论区大佬们提供的新的方法,拓展眼界!

    兼容和polypfill,如下:

1.png

2.png