精读《JavaScript 高级程序设计 第4版》第11章 期约与异步函数(三)
·
11.1.3 期约连锁与期约合成
多个期约组合在一起可以构成强大的代码逻辑。这种组合可以通过两种方式实现:期约连锁与期约合成。前者就是一个期约接一个期约的拼接,后者则是将多个期约组合为一个期约。
1、期约连锁
每个期约实例的方法(then()、catch()和finally())都会返回一个新的期约对象,而这个新期约又有自己的实例方法。这样连缀方法调用就可以构成所谓的“期约连锁”。
// 基础示例
fetch('/api/users')
.then(response => response.json())
.then(users => {
console.log(users);
return fetch(`/api/users/${users[0].id}`);
})
.then(response => response.json())
.then(userDetail => {
console.log(userDetail);
})
.catch(error => {
console.error('请求失败:', error);
});
以上这种方式使得每个后续的处理程序都会等待前一个期约解决,然后实例化一个新期约并返回它。这种结构可以简洁地将异步任务串行化。
2. 期约合成(Promise Composition)
(1)Promise.all() - 传统方式
该静态方法创建的期约会在一组期约全部解决之后再解决,接收一个可迭代对象,返回一个新期约。可迭代对象中的元素会 通过Promise.resolve()转换为期约。
// 等待所有期约完成
const promise1 = fetch('/api/users');
const promise2 = fetch('/api/posts');
const promise3 = fetch('/api/comments');
Promise.all([promise1, promise2, promise3])
.then(responses => {
// responses是一个包含所有结果的数组
return Promise.all(responses.map(r => r.json()));
})
.then(([users, posts, comments]) => {
console.log('所有数据:', { users, posts, comments });
})
.catch(error => {
// 任何一个期约被拒绝都会进入这里
console.error('某个请求失败:', error);
});
如果至少有一个包含的期约待定,则合成的期约也会待定;如果有一个包含的期约拒绝,则合成的期约也会拒绝;如果所有期约都成功解决,则合成的期约解决值就是所有包含期约解决值的数组,按照迭代器顺序。如果有期约拒绝,则第一个拒绝的期约会将自己的理由作为合成期约的拒绝理由,之后的不影响最终的拒绝理由。不过这并不影响所有包含期约正常的拒绝操作。
(2)Promise.allSettled()
ES2020引入的Promise.allSettled()提供了更精细的控制:
const promises = [
fetch('/api/users'),
fetch('/api/posts'),
fetch('/invalid-url') // 这个会失败
];
Promise.allSettled(promises)
.then(results => {
const successful = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
console.log(`成功: ${successful.length}, 失败: ${failed.length}`);
// 只处理成功的请求
return Promise.all(
successful.map(r => r.value.json())
);
})
.then(data => {
console.log('成功的数据:', data);
});
(3)Promise.race() - 竞速模式
这个静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。接收一个可迭代对象,返回一个新期约。无论是解决还是拒绝,只要是第一个落定的期约,Promise.race() 就会包装其解决值或拒绝理由并返回新期约。
// 获取最快响应的数据源
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), 5000);
});
const dataPromise = fetch('/api/data');
Promise.race([dataPromise, timeoutPromise])
.then(response => {
console.log('数据获取成功');
return response.json();
})
.catch(error => {
console.error('请求超时或失败:', error);
});
(4)Promise.any() - ES2021新特性
// 等待第一个成功的期约
const primaryAPI = fetch('/api/primary/data');
const backupAPI = fetch('/api/backup/data');
const cacheAPI = fetch('/api/cache/data');
Promise.any([primaryAPI, backupAPI, cacheAPI])
.then(firstResponse => {
console.log('从最快可用的数据源获取数据');
return firstResponse.json();
})
.then(data => {
console.log('数据:', data);
})
.catch(error => {
// 所有期约都失败时才会进入这里
console.error('所有数据源都不可用:', error);
});
3、串行期约合成
后续期约使用之前期约的返回值来串联期约,这很像函数合成,即将多个函数合并成一个函数:
function addTwo(x) { return x + 2; }
function addThree(x) { return x + 3; }
function addFive(x) { return x + 5; }
function compose(...fns){
return (x) => fns.reduce((promise, fn) => promise.then(fn), Promise.resolve(x))
}
let addTen = compose(addTwo, addThree, addFive);
addTen(8).then(console.log); //18
4. 高级期约模式
动态期约连锁
class AsyncPipeline {
constructor(initialValue) {
this.promise = Promise.resolve(initialValue);
}
then(fn) {
this.promise = this.promise.then(fn);
return this;
}
catch(fn) {
this.promise = this.promise.catch(fn);
return this;
}
finally(fn) {
this.promise = this.promise.finally(fn);
return this;
}
get() {
return this.promise;
}
}
// 使用示例
new AsyncPipeline(10)
.then(x => x * 2)
.then(x => fetch(`/api/process/${x}`))
.then(response => response.json())
.then(data => data.result)
.catch(error => {
console.error('处理失败:', error);
return { result: 0 }; // 默认值
})
.get()
.then(finalResult => {
console.log('最终结果:', finalResult);
});
带缓存的期约合成
class PromiseWithCache {
constructor() {
this.cache = new Map();
}
getOrCreate(key, promiseFactory) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const promise = promiseFactory()
.then(result => {
// 缓存成功结果
this.cache.set(key, Promise.resolve(result));
return result;
})
.catch(error => {
// 从缓存中移除失败的期约
this.cache.delete(key);
throw error;
});
this.cache.set(key, promise);
return promise;
}
}
// 使用示例
const cache = new PromiseWithCache();
async function getUserData(userId) {
return cache.getOrCreate(`user-${userId}`, () =>
fetch(`/api/users/${userId}`).then(r => r.json())
);
}更多推荐



所有评论(0)