再有人问Promise,我就用这 9 个高级用法闪瞎他的眼!

概述

想象一下你点外卖:下单后,你并不知道外卖什么时候送达,也不知道会不会出问题。 Promise 就类似于这个外卖订单的“状态记录器”。它代表着一个异步操作(比如网络请求、文件读取)的最终结果。

这个“状态记录器”总共有三种状态:

  • 等待中 (Pending): 就像你刚下单,外卖还在准备中,不知道最终结果如何。
  • 已完成 (Fulfilled): 外卖送到了,你成功拿到餐食了!
  • 已拒绝 (Rejected): 外卖出问题了,比如商家缺货、配送员出了意外,你没拿到外卖。

传统的回调函数处理异步操作就像你一直盯着手机等外卖,还要处理各种可能的情况(例如:超时、商家联系不上等等)。而Promise就像一个更高级的“外卖监控系统”,它让你不用一直盯着,系统会自动通知你外卖的状态变化:

  • 它会在事件循环完成后才通知你结果(也就是你不会一直刷新页面查看状态)。
  • 即使外卖送达(无论成功或失败),你之前添加的“监控” then() 仍然会收到通知。
  • 你可以添加多个“监控”,它们会按照添加顺序依次收到通知。

Promise 最强大的功能是链式调用,就像你可以把外卖送达后“吃午餐”,“洗碗”这些步骤串联起来一样,一个步骤完成后自动执行下一个步骤,让整个流程变得清晰流畅。

常见方法

Promise 提供了几个常用的静态方法来方便地创建和操作多个 Promise:

  • Promise.all(promises): 同时等待多个 Promise 完成,只有全部完成才返回结果,任何一个失败则整个操作失败。 就像同时点了几家外卖,只有全部送达你才能开始吃饭。
  • Promise.allSettled(promises): 等待所有 Promise 完成,无论成功还是失败都返回结果,就像你点了几家外卖,无论哪家出了问题,你都会知道结果。
  • Promise.any(promises): 等待多个 Promise 中的任意一个完成,只要有一个成功就返回结果,全部失败才算失败。 就像你同时点了几家外卖,只要有一家送达,你就可以先吃。
  • Promise.race(promises): 等待多个 Promise 中最先完成的一个,无论成功还是失败。 就像你同时点了几家外卖,先到的一家决定你的用餐时间。
  • Promise.resolve(value): 创建一个已完成的Promise,直接返回value。就像外卖已经提前送到。
  • Promise.reject(reason): 创建一个已拒绝的Promise,直接返回reason。就像外卖送餐途中出事了。

此外,Promise 实例本身也有一些重要的方法,例如 then() 用于添加成功回调,catch() 用于添加失败回调,finally() 无论成功失败都会执行的回调。 这些方法共同构成了 Promise 的核心功能,提供了处理异步操作的强大机制。

基础用法

1. Promise.all([])

当数组中所有 Promise 实例都成功时,它返回一个包含成功结果的数组,顺序与请求顺序相同。如果任何一个 Promise 失败,则进入失败回调。

const p1 = new Promise((resolve) => {
    resolve(1);
});
const p2 = new Promise((resolve) => {
    resolve(1);
});
const p3 = Promise.resolve('ok');

// 如果所有 Promise 都成功,result 将是一个包含 3 个结果的数组。
const result = Promise.all([p1, p2, p3]);
// 如果其中一个失败,result 将是失败 Promise 的值。
2. Promise.allSettled([])

执行不会失败;它返回一个数组,该数组对应于输入数组中每个 Promise 实例的状态。

const p1 = Promise.resolve(1);
const p2 = Promise.reject(-1);
Promise.allSettled([p1, p2]).then(res => {
    console.log(res);
});
// 输出:
/*
   [
    { status: 'fulfilled', value: 1 },
    { status: 'rejected', reason: -1 }
   ]
*/
3. Promise.any([])

如果输入数组中的任何一个 Promise 完成,则返回的实例将变为已完成状态,并返回第一个已完成 Promise 的值。如果所有 Promise 都被拒绝,则返回的实例将变为已拒绝状态。

const p1 = new Promise((resolve, reject) => {
    reject(1);
});
const p2 = new Promise((resolve, reject) => {
    reject(2);
});
const p3 = Promise.resolve("ok");

Promise.any([p1, p2, p3]).then(
    (r) => console.log(r), // 输出 'ok'
    (e) => console.log(e)
);
4. Promise.race([])

只要数组中的任何一个 Promise 状态发生变化,race 方法的状态也会相应地发生变化;第一个发生变化的 Promise 的值将传递给 race 方法的回调函数。

const p1 = new Promise((resolve) => {
    setTimeout(() => {
        resolve(10);
    }, 3000);
});
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        throw new Error("I encountered an error");
    }, 2000);
});

Promise.race([p1, p2]).then(
    (v) => console.log(v), // 输出 10
    (e) => console.log(e)
);

抛出异常不会改变 race 的状态;它仍然由 p1 决定。

高级用法

以下列出了9种高级用法,可以帮助开发者更高效、更优雅地处理异步操作。 每个用法都配有代码示例和详细解释。

1. 并发控制

使用 Promise.all 可以并行执行多个 Promise,但为了控制同时请求的数量,可以实现一个并发控制函数。 例如,限制同时进行的网络请求数量,避免服务器过载。

const concurrentPromises = async (promises, limit) => {
    const results = [];
    let running = 0;
    const next = () => {
        if (promises.length === 0) return;
        const p = promises.shift();
        running++;
        p().then(res => {
            results.push(res);
            running--;
            next();
        }).catch(err => {
            console.error("Error in concurrent promise:", err);
            running--;
            next();
        });
    };
    for (let i = 0; i < limit; i++) {
        next();
    }
    return new Promise(resolve => {
        const check = () => {
            if (running === 0 && promises.length === 0) {
                resolve(results);
            } else {
                setTimeout(check, 10); //检查是否所有Promise都执行完成
            }
        };
        check();
    });
};


const promises = [
    () => new Promise(resolve => setTimeout(() => resolve('promise1'), 1000)),
    () => new Promise(resolve => setTimeout(() => resolve('promise2'), 2000)),
    () => new Promise(resolve => setTimeout(() => resolve('promise3'), 500)),
    () => new Promise(resolve => setTimeout(() => resolve('promise4'), 1500)),
    () => new Promise(resolve => setTimeout(() => resolve('promise5'), 800))
];

concurrentPromises(promises, 2).then(results => console.log(results));
//输出结果顺序可能因并发执行而改变
2. Promise 超时

有时,您可能希望 Promise 在一定时间内未完成则自动拒绝。 这可以用于防止长时间等待无响应的请求。

const promiseWithTimeout = (promise, ms) =>
    Promise.race([
        promise,
        new Promise((resolve, reject) => setTimeout(() => reject(new Error('Timeout after ' + ms + 'ms')), ms))
    ]);

const myPromise = new Promise(resolve => setTimeout(() => resolve('success'), 3000));

promiseWithTimeout(myPromise, 2000)
  .then(result => console.log(result)) // 可能输出 'success',也可能超时
  .catch(error => console.error(error)); // 可能输出超时错误
3. 取消 Promise

原生 JavaScript Promise 无法取消,但可以通过引入可控中断逻辑来模拟取消。 这对于需要在中途停止操作的情况非常有用。

let isCanceled = false;
const cancellablePromise = (promise) => {
  return new Promise((resolve, reject) => {
    promise.then(value => {
      if (isCanceled) {
        reject({ isCanceled: true, value });
      } else {
        resolve(value);
      }
    }).catch(error => {
      if (isCanceled) {
        reject({ isCanceled: true, error });
      } else {
        reject(error);
      }
    });
  })
};
//测试案例
const p = cancellablePromise(new Promise(res => setTimeout(() => res('OK'), 3000)));
setTimeout(() => isCanceled = true, 1000);
p.then(console.log).catch(console.error) // 输出错误信息和isCanceled
4. 顺序执行 Promise 数组

有时您需要按顺序执行一系列 Promise,确保先前的异步操作完成之后才能开始下一个操作。 这对于依赖关系明确的任务非常重要。

const sequencePromises = async (promises) => {
    let result = null;
    for (const promise of promises) {
        result = await promise();
    }
    return result;
};

const promises = [
    () => new Promise(resolve => setTimeout(() => resolve('promise1'), 1000)),
    () => new Promise(resolve => setTimeout(() => resolve('promise2'), 500)),
    () => new Promise(resolve => setTimeout(() => resolve('promise3'), 2000))
];

sequencePromises(promises).then(result => console.log(result)); // 输出 promise3
5. Promise 的重试逻辑

当 Promise 因临时错误而被拒绝时,您可能希望重试其执行。 这对于网络请求等易受干扰的操作非常有用。

const retryPromise = async (promiseFn, maxAttempts, interval) => {
    let attempts = 0;
    while (attempts < maxAttempts) {
        try {
            const result = await promiseFn();
            return result;
        } catch (error) {
            console.error(`Attempt ${attempts + 1} failed:`, error);
            attempts++;
            await new Promise(resolve => setTimeout(resolve, interval));
        }
    }
    throw new Error('Max attempts reached');
};

const unreliablePromise = () => new Promise((resolve, reject) => {
    if (Math.random() < 0.5) {
        resolve('Success!');
    } else {
        reject(new Error('Failed!'));
    }
});

retryPromise(unreliablePromise, 3, 1000)
    .then(result => console.log(result))
    .catch(error => console.error(error));
6. 确保 Promise 只解析一次

在某些情况下,您可能希望确保 Promise 只解析一次,即使 resolve 被多次调用。 这可以防止意外的重复操作。

const onceResolvedPromise = (executor) => {
    let resolved = false;
    return new Promise((resolve, reject) => {
        executor(value => {
            if (!resolved) {
                resolved = true;
                resolve(value);
            }
        }, reject);
    });
};


const myPromise = onceResolvedPromise((resolve) => {
    resolve('Success!');
    resolve('Another Success!'); //这个resolve不会被执行
});

myPromise.then(result => console.log(result)); // 输出 Success!
7. 使用 Promise 代替回调函数

Promise 提供了一种更标准化、更方便的方式来处理异步操作,取代回调函数,使代码更易读和维护。

const callbackToPromise = (fn, ...args) => {
    return new Promise((resolve, reject) => {
        fn(...args, (error, result) => {
            if (error) {
                reject(error);
            } else {
                resolve(result);
            }
        });
    });
};

// 模拟一个使用回调函数的异步操作
const asyncOperation = (callback) => {
    setTimeout(() => {
        const result = Math.random() > 0.5 ? 'Success' : 'Failure';
        callback(result === 'Failure' ? new Error('Operation failed') : null, result);
    }, 1000);
};

callbackToPromise(asyncOperation)
    .then(result => console.log('Success:', result))
    .catch(error => console.error('Error:', error));
8. 动态生成 Promise 链

在某些情况下,您可能需要根据不同的条件动态创建一系列 Promise 链。 这对于需要根据运行时数据决定执行流程的场景非常有用。

const tasks = [
    () => new Promise(resolve => setTimeout(() => resolve('Task 1'), 1000)),
    () => new Promise(resolve => setTimeout(() => resolve('Task 2'), 500)),
    () => new Promise(resolve => setTimeout(() => resolve('Task 3'), 2000))
];


const dynamicPromiseChain = async (tasks, condition) => {
    let result = null;
    for(const task of tasks){
        if(!condition){
            break;
        }
        result = await task();
    }
    return result;
};


dynamicPromiseChain(tasks, true)
  .then(result => console.log('Result:', result))
  .catch(error => console.error('Error:', error));

dynamicPromiseChain(tasks, false)
  .then(result => console.log('Result:', result))
  .catch(error => console.error('Error:', error));
9. 使用 Promise 实现简单的异步锁

在多线程环境中,可以使用 Promise 来实现简单的异步锁,确保只有一个任务可以同时访问共享资源。

let lock = Promise.resolve();

const acquireLock = async () => {
    await lock;
    return new Promise(resolve => {
        lock = new Promise(r => setTimeout(() => {r(); lock = Promise.resolve(); resolve();},1000));
    });
};

const accessResource = async () => {
    await acquireLock();
    console.log('Accessing resource...');
    await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟访问资源
    console.log('Finished accessing resource.');
};

const runTask = async () => {
    await accessResource();
}

const run = async ()=>{
    await runTask();
    await runTask();
}

run();

这些示例展示了 Promise 高级用法的具体实现,可以帮助开发者更好地理解和应用这些技巧。 请注意,实际应用中可能需要根据具体情况进行调整。

结论

Promise 是现代 JavaScript 异步编程中不可或缺的一部分。掌握其高级技巧将极大地提高开发效率和代码质量。通过上述各种方法,开发者可以更自信地处理复杂的异步场景,编写更易读、更优雅、更健壮的代码。