Promise高级特性

本章将介绍Promise的一些高级特性和新版本ECMAScript中新增的功能,帮助您更深入地理解和使用Promise。

6.1 Promise构造函数的高级用法

Promise构造函数除了基本用法外,还有一些高级技巧可以帮助我们更好地控制异步操作。

Promise的延迟执行

通过将Promise的创建包装在函数中,可以实现延迟执行。

示例:延迟执行的Promise

// 创建延迟执行的Promise
function delayedPromise(value, delay) {
    return () => new Promise(resolve => {
        setTimeout(() => resolve(value), delay);
    });
}

// 使用延迟执行的Promise
const delayedPromises = [
    delayedPromise('第一个', 1000),
    delayedPromise('第二个', 2000),
    delayedPromise('第三个', 3000)
];

// 按顺序执行
delayedPromises.reduce((chain, delayedPromise) => {
    return chain.then(results => {
        return delayedPromise().then(result => [...results, result]);
    });
}, Promise.resolve([]))
.then(results => {
    console.log('按顺序执行结果:', results);
});

Promise的取消机制

虽然原生Promise不支持取消,但可以通过AbortController等机制实现取消功能。

示例:可取消的Promise

// 创建可取消的Promise
function cancellablePromise(promiseFn, signal) {
    return new Promise((resolve, reject) => {
        // 检查是否已经取消
        if (signal.aborted) {
            reject(new Error('操作已被取消'));
            return;
        }
        
        // 监听取消信号
        signal.addEventListener('abort', () => {
            reject(new Error('操作已被取消'));
        });
        
        // 执行原始Promise
        promiseFn().then(resolve).catch(reject);
    });
}

// 使用可取消的Promise
const controller = new AbortController();
const { signal } = controller;

const cancellableFetch = cancellablePromise(
    () => fetch('/api/data'),
    signal
);

// 设置超时取消
const timeoutId = setTimeout(() => {
    controller.abort();
}, 5000);

cancellableFetch
    .then(response => {
        clearTimeout(timeoutId);
        console.log('获取数据成功:', response);
    })
    .catch(error => {
        clearTimeout(timeoutId);
        if (error.message === '操作已被取消') {
            console.log('请求已被取消');
        } else {
            console.error('请求失败:', error);
        }
    });

6.2 AggregateError

AggregateError是ES2021中引入的错误类型,用于表示多个错误的集合,主要与Promise.any配合使用。

示例:AggregateError的使用

// Promise.any失败时会抛出AggregateError
const promises = [
    Promise.reject(new Error('错误1')),
    Promise.reject(new Error('错误2')),
    Promise.reject(new Error('错误3'))
];

Promise.any(promises)
    .then(result => {
        // 不会执行到这里
        console.log(result);
    })
    .catch(error => {
        if (error instanceof AggregateError) {
            console.log('所有Promise都失败了:');
            error.errors.forEach((err, index) => {
                console.log(`  ${index + 1}. ${err.message}`);
            });
        } else {
            console.error('其他错误:', error);
        }
    });

// 手动创建AggregateError
const errors = [
    new Error('第一个错误'),
    new Error('第二个错误'),
    new Error('第三个错误')
];

const aggregateError = new AggregateError(errors, '多个操作失败');
console.log(aggregateError.message); // 输出: 多个操作失败
console.log(aggregateError.errors); // 输出: [Error, Error, Error]

6.3 Promise.try()提案

Promise.try是一个提案中的方法,用于同步或异步代码统一处理。

示例:Promise.try的模拟实现

// 模拟Promise.try的实现
Promise.try = function(callback) {
    return new Promise((resolve, reject) => {
        try {
            const result = callback();
            resolve(result);
        } catch (error) {
            reject(error);
        }
    });
};

// 使用Promise.try处理同步和异步代码
Promise.try(() => {
    // 同步代码
    if (Math.random() > 0.5) {
        return '同步结果';
    } else {
        // 异步代码
        return new Promise(resolve => {
            setTimeout(() => resolve('异步结果'), 1000);
        });
    }
})
.then(result => {
    console.log('操作结果:', result);
})
.catch(error => {
    console.error('操作失败:', error);
});

6.4 Promise的性能优化

在处理大量Promise时,需要注意性能优化问题。

避免创建不必要的Promise

示例:避免不必要的Promise创建

// 不好的做法:创建不必要的Promise
function getValue(value) {
    return Promise.resolve(value); // 如果value已经是确定的值
}

// 好的做法:只在必要时创建Promise
function getValue(value) {
    if (value instanceof Promise) {
        return value;
    }
    return Promise.resolve(value);
}

// 或者使用工具函数
function promisify(value) {
    return value instanceof Promise ? value : Promise.resolve(value);
}

批量处理优化

示例:批量处理优化

// 优化大量Promise的处理
function optimizedBatchProcess(items, processor, concurrency = 5) {
    const results = [];
    const executing = [];
    
    for (const item of items) {
        const promise = Promise.resolve().then(() => processor(item)).then(result => {
            results.push({ item, result });
        });
        
        executing.push(promise);
        
        // 限制并发数
        if (executing.length >= concurrency) {
            await Promise.race(executing);
            executing.splice(executing.findIndex(p => p === promise), 1);
        }
    }
    
    await Promise.all(executing);
    return results;
}

// 使用优化的批量处理
const items = Array.from({ length: 1000 }, (_, i) => i);
optimizedBatchProcess(items, async (item) => {
    // 模拟异步处理
    await new Promise(resolve => setTimeout(resolve, 10));
    return item * 2;
}, 10)
.then(results => {
    console.log('处理完成,结果数量:', results.length);
});

6.5 Promise与微任务队列

理解Promise与微任务队列的关系对于掌握JavaScript的执行机制非常重要。

示例:Promise与微任务队列

console.log('1');

setTimeout(() => {
    console.log('2');
}, 0);

Promise.resolve().then(() => {
    console.log('3');
});

Promise.resolve().then(() => {
    console.log('4');
});

console.log('5');

// 输出顺序: 1, 5, 3, 4, 2

// 更复杂的例子
console.log('start');

setTimeout(() => {
    console.log('setTimeout1');
}, 0);

Promise.resolve().then(() => {
    console.log('promise1');
    return Promise.resolve();
}).then(() => {
    console.log('promise2');
});

setTimeout(() => {
    console.log('setTimeout2');
}, 0);

console.log('end');

// 输出顺序: start, end, promise1, promise2, setTimeout1, setTimeout2

6.6 Promise的调试技巧

调试Promise代码时有一些有用的技巧可以帮助我们更快地定位问题。

添加调试标签

示例:Promise调试标签

// 为Promise添加调试标签
function debugPromise(promise, label) {
    return promise.then(
        result => {
            console.log(`[成功] ${label}:`, result);
            return result;
        },
        error => {
            console.error(`[失败] ${label}:`, error);
            throw error;
        }
    );
}

// 使用调试标签
const fetchUserPromise = debugPromise(fetchUser(123), '获取用户');
const fetchPostsPromise = debugPromise(fetchUserPosts(123), '获取帖子');

Promise.all([fetchUserPromise, fetchPostsPromise])
    .then(([user, posts]) => {
        console.log('所有数据获取完成');
    });

Promise链的可视化

示例:Promise链可视化

// 创建可追踪的Promise
function traceablePromise(promise, name) {
    console.log(`[开始] ${name}`);
    return promise
        .then(result => {
            console.log(`[完成] ${name}`);
            return result;
        })
        .catch(error => {
            console.error(`[错误] ${name}:`, error);
            throw error;
        });
}

// 使用可追踪的Promise
traceablePromise(
    fetchUserData(userId)
        .then(user => traceablePromise(
            processUserData(user),
            '处理用户数据'
        ))
        .then(processedData => traceablePromise(
            saveUserData(processedData),
            '保存用户数据'
        )),
    '完整用户处理流程'
)
.then(() => {
    console.log('用户处理流程完成');
})
.catch(error => {
    console.error('用户处理流程失败:', error);
});

6.7 Promise与其他异步模式的结合

Promise可以与Generator、async/await等其他异步模式结合使用。

Promise与Generator

示例:Promise与Generator结合

// 使用Generator控制Promise执行
function* asyncGenerator() {
    try {
        const user = yield fetchUser(123);
        console.log('用户:', user);
        
        const posts = yield fetchUserPosts(user.id);
        console.log('帖子:', posts);
        
        const comments = yield fetchPostComments(posts[0].id);
        console.log('评论:', comments);
        
        return { user, posts, comments };
    } catch (error) {
        console.error('执行过程中出错:', error);
        throw error;
    }
}

// 执行Generator
function runAsyncGenerator(generator) {
    return new Promise((resolve, reject) => {
        const it = generator();
        
        function handle(result) {
            if (result.done) {
                resolve(result.value);
                return;
            }
            
            Promise.resolve(result.value)
                .then(
                    res => handle(it.next(res)),
                    err => {
                        it.throw(err);
                        reject(err);
                    }
                );
        }
        
        handle(it.next());
    });
}

// 使用
runAsyncGenerator(asyncGenerator())
    .then(result => {
        console.log('所有数据获取完成:', result);
    })
    .catch(error => {
        console.error('执行失败:', error);
    });

6.8 Promise的未来发展方向

Promise作为JavaScript异步编程的核心,仍在不断发展和完善中。

当前提案

  • Promise.try:提供统一的同步/异步代码处理方式
  • Promise.prototype.finally:已在ES2018中标准化
  • Promise.allSettled:已在ES2020中标准化
  • Promise.any:已在ES2021中标准化

最佳实践演进

  • 从回调函数到Promise
  • 从Promise到async/await
  • 从单一Promise到Promise组合
  • 从简单错误处理到精细化错误管理

提示:随着JavaScript的发展,新的异步编程模式不断涌现,但理解Promise的核心原理仍然是掌握现代JavaScript异步编程的基础。