Promise状态控制权外置

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 的状态变化

- 在某些情况下,希望在创建 Promise 时不立即执行某些逻辑,而是在稍后的某个时间点控制何时执行和如何执行。这时,可以通过将执行逻辑封装在另一个函数中,然后在需要的时候调用这个函数来控制 resolve 和 reject 的调用。

- 将 Promise 的 resolve 和 reject 控制权从 Promise 构造函数中分离出来的编程模式。它创建了一个包含 Promise 及其解决方法的对象,允许你在 Promise 外部控制其状态

5、现有解法

- 可以封装,拆解出 promise deferred对象

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

    - promise - 标准的 Promise 对象

    - resolve - 用于解决 Promise 的函数

    - reject - 用于拒绝 Promise 的函数

- 封装输出:

    function getPromise() {
        let resolve, reject;
        const promise = new Promise((res, rej) => {
            resolve = res;
            reject = rej;
        });
    
        return { promise, resolve, reject };
    }
  • 与常态 Promise 的对比
特性 标准 Promise Deferred 模式的 Promise
控制权 在构造函数内部 外部可控制
解决时机 必须在构造函数中决定 可在任何时间、任何地点决定
适用场景 已知的异步操作 需要外部控制的异步操作

6、常用应用场景

6.1. 用户交互控制

           function createConfirmationDialog() {
                const {promise, resolve, reject} = getPromise();
                
                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('用户取消');
                }
            });

6.2. 文件加载

            function loadImg(){
                const {promise, resolve, reject} = getPromise();
                
                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, "图片加载失败了~~"))

6.3. 异步操作队列

            class AsyncQueue {
                constructor() {
                    this.queue = [];
                    this.current = null;
                }
                
                add(task) {
                    const deferredPromise = getPromise();
                    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());
                }
            }

6.4. API 请求超时控制

      function fetchWithTimeout(url, timeout) {
            const {promise, resolve, reject} = getPromise();
            
            fetch(url)
                .then(response => resolve(response))
                .catch(error => reject(error));
            
            setTimeout(() => {
                reject(new Error('request timeout'));
            }, timeout);
            
            return promise;
      }

7、 现代 JavaScript 中的替代方案

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

  • AbortController - 用于取消 fetch 请求
    const controller = new AbortController();
    fetch(url, { signal: controller.signal });
    controller.abort(); // 取消请求
  • async/await - 简化异步流程控制
    async function handleUserAction() {
        try {
        const result = await someAsyncOperation();
        // 处理结果
        } catch (error) {
        // 处理错误
        }
    }
  • 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() 方法也是和上述主题有一样的效果。

兼容和polypfill,如下:

兼容

polyfill