总结
Promise 是一种异步解决方案, 在异步流程中,如果想在未来某一时刻执行某项操作, 可以通过回调或事件的方式处理, 但存在嵌套过深的场景。
通过Promise,可以通过链式链路进行操作, 简化了异步操作的控制; ES6 提供了原生的解决方案。
如果自己完成一个Promise解决方案, 应该如何去处理呢?
简介
异步编程的一种解决方案。 ES6统一了用法,原生提供了Promise对象。
特点:
- 对象的状态不受外界影响;三种状态:Pending、fulfilled、rejected;
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果;
基本用法
// Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
// 会继续执行这行代码
// 但最好不要这样做,在 then 里执行就好了;
console.log(2);
});
// then 接受两个回调函数作为参数:
// 分别指定resolved状态和rejected状态的回调函数。
// 第二个方法可选
promise.then(function(value) {
// success
}, function(error) {
// failure
});
异步加载图片
function loadImageAsync(url) {
// 返回一个 Promise 实例
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
Promise.prototype.then()
添加状态改变时的回调函数。
then方法返回的是一个新的Promise实例。
如果返回的是一个 Promise 实例, 会等待该Promise对象的状态发生变化,才会被调用。
loadImageAsync('https://imshengli.com/images/logo.png')
.then(image => {
console.log(image);
return image;
}).then(res => {
console.log(res);
});
Promise.prototype.catch()
是.then(null, rejection)的别名,
用于指定发生错误时的回调函数,
返回的还是 Promise 对象。
then方法指定的回调函数,如果运行中抛出错误,
也会被catch方法捕获。
Promise 对象的错误具有“冒泡”性质, 会一直向后传递,直到被捕获为止。
建议总是使用catch方法,而不使用then方法的第二个参数。
跟传统的try/catch代码块不同的是, 如果没有使用catch方法指定错误处理的回调函数, Promise 对象内部抛出的错误不会传递到外层代码, 即不会有任何反应。
// 不会退出进程、终止脚本执行,
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
Promise.all()
将多个 Promise 实例,包装成一个新的 Promise 实例。
// 参数可以不是数组,但必须具有 Iterator 接口,
// 且返回的每个成员都是 Promise 实例;
// 如果不是,就会先调用的Promise.resolve方法,
// 将参数转为 Promise 实例。
const p = Promise.all([p1, p2, p3]);
// 都变成fulfilled,p的状态才会变成fulfilled,
// 有一个被rejected,p的状态就变成rejected。
// 如果 作为参数的 Promise 实例,
// 自己定义了catch方法,那么它一旦被rejected,
// 并不会触发Promise.all()的catch方法。
Promise.race()
将多个 Promise 实例,包装成一个新的 Promise 实例。
// 只要p1、p2、p3之中有一个实例率先改变状态,
// p的状态就跟着改变。
// 那个率先改变的 Promise 实例的返回值,
// 就传递给p的回调函数。
const p = Promise.race([p1, p2, p3]);
Promise.resolve()
将现有对象转为 Promise 对象。
// 将 jQuery 生成的deferred对象,转为一个新的 Promise 对象。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
参数:
- 参数是一个 Promise 实例: 直接返回;
- 参数是一个thenable对象:具有 then 方法,将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法;
- 参数不是具有then方法的对象,或根本就不是对象:返回一个新的 Promise 对象,状态为resolved;
- 不带有任何参数:直接返回一个resolved状态的 Promise 对象;
注意:
立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时, 而不是在下一轮“事件循环”的开始时。 setTimeout(fn, 0)在下一轮“事件循环”开始时执行。
Promise.reject()
返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
Promise.reject()方法的参数, 会原封不动地作为reject的理由,变成后续方法的参数。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
done()
自行添加: 总是处于回调链的尾端,保证抛出任何可能出现的错误。
实现:
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};
finally()
自行添加: 不管 Promise 对象最后状态如何,都会执行的操作。
实现:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
Promise.try()
自行添加: 不知道或者不想区分,函数f是同步函数还是异步操作, 但是想用 Promise 来处理它。
// 缺点,就是如果f是同步函数,
// 那么它会在本轮事件循环的末尾执行。
Promise.resolve().then(f);
改进:
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
Promise.allSettled()
ES2020 引入。等待所有 Promise 都完成(无论 fulfilled 还是 rejected),返回每个结果的状态。
与 Promise.all 的区别:all 遇到第一个 reject 就短路,allSettled 永远等所有都结束。
const promises = [
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/invalid-url') // 这个会失败
];
Promise.allSettled(promises).then(results => {
// results 的每一项都有 status 字段
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
});
// 适用场景:并发请求多个接口,不希望一个失败影响其他结果
Promise.any()
ES2021 引入。只要有一个 fulfilled 就返回该结果;全部 rejected 才抛出 AggregateError。
const promises = [
fetch('https://cdn1.example.com/data.json'),
fetch('https://cdn2.example.com/data.json'),
fetch('https://cdn3.example.com/data.json')
];
// 最快成功的那个 CDN 的结果
Promise.any(promises)
.then(response => console.log('最快成功:', response))
.catch(err => {
// 只有全部失败才走这里
console.log(err instanceof AggregateError); // true
console.log(err.errors); // 所有错误的数组
});
Promise.all / race / allSettled / any 对比:
| 方法 | 短路条件 | 返回值 |
|---|---|---|
all | 任意一个 reject | 所有 fulfilled 值的数组 |
race | 任意一个 settle | 第一个 settle 的值 |
allSettled | 不短路 | 所有结果的状态数组 |
any | 任意一个 fulfill | 第一个 fulfilled 值 |
错误处理最佳实践
// 1. 始终在链的末尾使用 catch
fetchData()
.then(process)
.then(save)
.catch(err => {
// 统一处理以上任意步骤的错误
console.error('Pipeline failed:', err);
});
// 2. async/await 配合 try-catch(推荐写法)
async function loadUser(id) {
try {
const response = await fetch(`/api/user/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (err) {
console.error('加载用户失败:', err);
throw err; // 向上层传播,或返回默认值
}
}
// 3. 避免在 then 里嵌套 Promise(反模式)
// 错误写法
fetchUser().then(user => {
fetchPosts(user.id).then(posts => {
// 嵌套地狱,又回去了
});
});
// 正确写法:利用链式调用
fetchUser()
.then(user => fetchPosts(user.id))
.then(posts => renderPosts(posts))
.catch(handleError);
手写简易 Promise
理解 Promise 的核心机制:状态管理 + 回调队列 + 异步执行。
class MyPromise {
constructor(executor) {
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status !== 'pending') return;
this.status = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
};
const reject = (reason) => {
if (this.status !== 'pending') return;
this.status = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 参数透传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
// 异步执行,模拟微任务
setTimeout(() => {
try {
const x = onFulfilled(this.value);
// 简化处理:如果返回的是 Promise 则等待它
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
} catch (err) {
reject(err);
}
}, 0);
};
const handleRejected = () => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
if (x instanceof MyPromise) {
x.then(resolve, reject);
} else {
resolve(x);
}
} catch (err) {
reject(err);
}
}, 0);
};
if (this.status === 'fulfilled') {
handleFulfilled();
} else if (this.status === 'rejected') {
handleRejected();
} else {
// pending 状态,先存储回调
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
}
// 验证
new MyPromise((resolve) => {
setTimeout(() => resolve('done'), 100);
})
.then(val => {
console.log(val); // 'done'
return 'next';
})
.then(val => console.log(val)); // 'next'
常见陷阱
1. 忘记 return
// 错误:then 里没有 return,下一个 then 收到 undefined
fetchUser()
.then(user => {
fetchPosts(user.id); // 忘记 return
})
.then(posts => {
console.log(posts); // undefined!
});
// 正确
fetchUser()
.then(user => {
return fetchPosts(user.id);
})
.then(posts => {
console.log(posts); // 正确拿到 posts
});
2. 在 catch 后面的 then 仍会执行
Promise.reject('error')
.catch(err => {
console.log('caught:', err);
// catch 返回的是 fulfilled 状态的 Promise
})
.then(() => {
// 这里会执行!因为上面 catch 正常返回了
console.log('继续执行');
});
3. Promise 构造函数是同步执行的
console.log('1');
new Promise((resolve) => {
console.log('2'); // 同步执行
resolve();
}).then(() => {
console.log('3'); // 微任务
});
console.log('4');
// 输出顺序:1 2 4 3
4. 循环引用导致死锁
const p = new Promise((resolve) => {
resolve(100);
});
const p2 = p.then(() => {
return p2; // TypeError: Chaining cycle detected
});
5. 并发控制
当需要限制并发数量时,不能直接用 Promise.all:
// 限制最大并发数的工具函数
async function asyncPool(limit, items, fn) {
const results = [];
const executing = [];
for (const item of items) {
const p = fn(item);
results.push(p);
if (items.length >= limit) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// 使用:最多同时 3 个请求
const urls = ['/api/1', '/api/2', '/api/3', '/api/4', '/api/5'];
asyncPool(3, urls, url => fetch(url));