Javascript Promise 与 async
- Javascript 编程语言
- Javascript 基础知识
- Javascript 更多引用类型
- Javascript 引用类型进阶知识
- Javascript 函数进阶知识
- Javascript 面向对象
- Javascript 错误处理
- Javascript 生成器 Generator
- Javascript Promise与async
- Javascript 模块 Module
- Javascript 浏览器

回调
回调方法的简介
回调函数是 JavaScript 中处理异步操作的传统方式,它是指作为参数传递给另一个函数并在特定条件满足时被调用的函数。
JavaScript 环境提供了许多使用回调函数的 API:
setTimeout(callback, delay)
:延迟执行回调函数。setInterval(callback, interval)
:定期执行回调函数。- 事件处理器:如
addEventListener(event, callback)
监听事件,执行回调。 Array
方法:如forEach
、map
、filter
等方法,数组遍历回调。Promise
对象:如.then(callback)
方法,在Promise 执行成功后回调。
示例:插入一个脚本的函数
js// 将动态创建的标签 <script src="..."> 插入到文档中 function loadScript(src, callback) { let script = document.createElement('script'); script.src = src; script.onload = () => callback(null, script); script.onerror = () => callback(new Error(`脚本加载失败: ${src}`)); document.head.append(script); } // 使用回调函数 loadScript('path/to/script.js', (error, script) => { if (error) { console.error('加载错误:', error); } else { console.log('脚本加载成功:', script.src); // 可以在这里使用加载的脚本中的功能 } });
JavaScript 社区形成了"错误优先回调"(Error-first Callback)的约定:
- 回调函数的第一个参数保留给错误对象
- 后续参数用于传递成功时的结果
- 成功时第一个参数传
null
或undefined
回调地狱
在回调中回调,则代码向右不断延伸,形成"金字塔"形状;
这种情况下,错误处理重复且冗长,代码可读性和可维护性差,难以追踪执行流程;
因此,这种情况被称为回调地狱。
js
loadScript('script1.js', (err, script1) => {
if (err) {
console.error(err);
} else {
loadScript('script2.js', (err, script2) => {
if (err) {
console.error(err);
} else {
loadScript('script3.js', (err, script3) => {
if (err) {
console.error(err);
} else {
// 所有脚本加载完成
startApplication();
}
});
}
});
}
});
解决方案:
-
命名函数解耦
jsfunction loadScript1(err) { if (err) return console.error(err); loadScript('script2.js', loadScript2); } function loadScript2(err) { if (err) return console.error(err); loadScript('script3.js', loadScript3); } function loadScript3(err) { if (err) return console.error(err); startApplication(); } loadScript('script1.js', loadScript1);
-
使用
Promise
链
Promise
Promise 是 JavaScript 中处理异步操作的核心对象,它提供了一种更优雅的方式来处理异步编程。
基本语法结构
-
创建 Promise :使用 Promise 对象的构造器:
jslet promise = new Promise(function(resolve, reject) { // executor(代码,"制作者") ... // 1 秒后发出工作已经被完成的信号,并带有结果 "done" setTimeout(() => resolve("done"), 1000); });
- 传递给
new Promise
的函数被称为 executor。 executor
包含两个由 JavaScript 引擎提供的回调函数:resolve(value)
- 表示操作成功完成reject(error)
- 表示操作失败
executor
运行结束后,如果成功则调用resolve
,如果出现error
则调用reject
。
- 传递给
-
Promise 内部属性:
-
状态
state
:状态 描述 触发条件 pending
初始状态,既不是成功也不是失败状态 创建 Promise 时的初始状态 fulfilled
操作成功完成 调用 resolve()
时rejected
操作失败 调用 reject()
时 -
result
------ 最初是undefined
,然后在resolve(value)
被调用时变为value
,或者在reject(error)
被调用时变为error
。 -
结果
result
:状态 描述 undefined
创建 Promise 时的初始状态 value
在 resolve(value)
被调用时变为value
error
在 reject(error)
被调用时变为error
Promise 对象的
state
和result
属性都是内部的,无法直接访问,但可对它们使用.then
/.catch
/.finally
方法。
-
-
then 方法:
-
第一个参数是一个函数,该函数将在
promise resolved
且接收到结果后执行。 -
第二个参数也是一个函数,该函数将在
promise rejected
且接收到 error 信息后执行。jspromise.then( function(result) { /* 处理成功结果 */ }, function(error) { /* 处理错误 */ } );
-
then
可以返回一个值,该值会传递给下一个then
处理程序。jsPromise.resolve("成功值") .then(result => { console.log("Then 收到:", result); return "新的值"; }) .then(result => { console.log("下一个 Then 收到:", result); // 收到 "新的值" });
-
-
catch 方法:
-
若只对
error
感兴趣,那可以用null
作为then
的第一个参数;.catch()
是.then(null, errorHandler)
的简写形式jslet promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Whoops!")), 1000); }); // .catch(f) 与 promise.then(null, f) 一样 promise.catch(error => console.error(error)); // 1 秒后显示 "Error: Whoops!"
-
catch
与then
一样,可以返回值,传给下一个then
处理程序。 -
catch
也可以抛出错误,传给下一个catch
处理程序。jsPromise.reject("错误A") .catch(err => { if (err === "错误A") { throw new Error("升级为错误B"); } return "普通处理"; }) .catch(err => { console.log("捕获:", err.message); // "捕获: 升级为错误B" return "最终处理结果"; }) .then(result => { console.log(result); // "最终处理结果" });
-
-
finally 方法:无论 Promise 成功或失败都会执行,执行清理/终结
-
finally
不会得到前一个处理程序的结果(它没有参数);如有结果,则该结果会被传递给了下一个合适的处理程序。 -
finally
不应该返回任何内容;若返回内容则会被忽略。jsPromise.resolve("成功值") .finally(arg => { console.log("Finally 收到:", arg); // undefined return "finally 返回值" }) .then(result => { console.log("下一个 Then 收到:", result); // "成功值" });
-
当
finally
抛出 error 时会覆盖之前的 error,同时会中断传递,执行将转到最近的 error 的处理程序。js// 案例1: 从成功状态抛出错误 Promise.resolve("成功值") .finally(() => { throw new Error("finally中的错误"); }) .then(result => console.log("成功:", result)) .catch(error => console.log("失败:", error.message)); // 案例2: 从失败状态抛出错误 Promise.reject(new Error("原始错误")) .finally(() => { throw new Error("finally中的新错误"); }) .catch(error => { console.log("捕获的错误:", error.message); // 显示 finally中的新错误 });
-
注意 ,一个 promise
内部在执行 resolve
或 reject
后,并不会立即终止函数的执行。这是 Promise 设计的一个重要特性。
- 避免不必要的执行中断开销
- 允许在
resolve
或reject
后执行清理操作
但在 JavaScript 的 Promise 实现中,多次调用 resolve
或 reject
是无效的,Promise 只会保持第一次调用的结果状态。
js
new Promise((resolve, reject) => {
console.log('1. Promise 构造函数开始执行');
resolve('成功'); // 这里不会停止执行
console.log('2. resolve 之后的代码仍然执行');
})
.then(result => {
console.log('3. then 回调执行:', result);
});
如果不想执行 resolve
或 reject
后的代码,可以使用 return
强行中断。
js
new Promise((resolve, reject) => {
return resolve('结果');
console.log('这行不会执行');
});
Promise 链
Promise 链是 JavaScript 中处理异步操作的核心机制,它通过 .then()
方法将多个异步操作串联起来,形成清晰可读的代码结构。
每个 .then
的调用都会返回了一个新的 promise,因此我们可以在其之上调用下一个 .then
。
调用 .then()
时,根据返回值类型不同,会有不同的操作:
- 返回普通值 → 自动包装为 resolved Promise
- 返回 Promise → 等待其 settled
- 返回 thenable 对象 → 按 Promise 处理
返回普通值
若有一系列的异步任务要一个接一个地执行,可创建一个 promise 链,它的想法是通过 .then
处理程序(handler)链进行传递 result。
js
new Promise(resolve => {
setTimeout(() => resolve(1), 1000);
})
.then(result => {
console.log(result); // 1
return result * 2;
})
.then(result => {
console.log(result); // 2
return result * 2;
})
.then(result => {
console.log(result); // 4
});
返回 Promise
若 .then()
返回值是 Promise,则会在该 Promise 结束后才会触发下一次 .then
。
js
new Promise(resolve => {
setTimeout(() => resolve(1000), 1000);
})
.then(result => {
console.log("接收到毫秒数", result) // 接收到毫秒数 1000
// 返回新Promise,将获取的 result 作为延迟执行的毫秒数
return new Promise(resolve => {
setTimeout(() => resolve("新 Promise"), result);
});
})
.then(console.log) // "新 Promise"
返回 thenable 对象
确切地说,处理程序返回的不完全是一个 promise,而是返回的被称为 thenable 对象 ------ 一个具有方法 .then
的任意对象。它会被当做一个 promise 来对待。
第三方库可以实现自己的 promise 兼容(promise-compatible)对象 。它们可以具有扩展的方法集,但也与原生的 promise 兼容,因为它们实现了 .then
方法。
这个特性允许我们将自定义的对象与 promise 链集成在一起,而不必继承自 Promise
。
这是一个 thenable 对象的示例:
jsclass Thenable { constructor(num) { this.num = num; } then(resolve, reject) { alert(resolve); // function() { native code } // 1 秒后使用 this.num*2 进行 resolve setTimeout(() => resolve(this.num * 2), 1000); // (**) } } new Promise(resolve => resolve(1)) .then(result => { return new Thenable(result); // (*) }) .then(alert); // 1000ms 后显示 2
避免回调地狱
用 Promise 重写上一节中的示例 loadScript
,避免回调地狱的产生。
js
function loadScript(src){
return new Promise()
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Error for ${src}`));
document.head.append(script);
});
}
loadScript("./tryLoad_1.js")
.then(
script => {
alert(`${script.src} is loaded!`);
return loadScript("./tryLoad_2.js");
},
error => alert(`Error:${error.message}`)
)
.then(script => alert(`${script.src} is loaded!`))
.catch(error => alert(`Error:${error.message}`));
promise 错误处理
上文有初步介绍过 catch
,当一个 promise 被 reject 或抛出错误时,控制权将移交至最近的 错误处理程序。
- 错误会沿着 Promise 链向下传播,直到遇到第一个
.catch()
处理程序 - 如果没有
.catch()
,则成为未处理的 rejection
正如所见,.catch
不必是立即的,它可能在一个或多个 .then
之后出现。
隐式 try...catch
promise 的 executor 周围的"隐式 try..catch
"会自动捕获 error,并将其变为 rejected promise。
无论在 new Promise(()=>{})
、.then()
、.catch()
还是 .finally
内抛出错误,都会转成类似如下面形式,最后交给下一个 错误处理程序。
js
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
未处理的 rejection
当发生一个常规的 error
并且未被 try..catch
捕获时,脚本会停止,并在控制台中留下了一个信息。对于在 Promise 中未被处理的 rejection
,也会发生类似的事。
JavaScript 引擎会跟踪此类 rejection
,在这种情况下会生成一个全局的 error
。
如果出现了一个 error,并且在这没有 .catch
,那么 unhandledrejection
处理程序就会被触发,并获取具有 error 相关信息的 event
对象。
js
// 浏览器环境
window.addEventListener('unhandledrejection', event => {
console.error('未处理的Promise错误:', event.reason);
event.preventDefault(); // 阻止默认错误输出
});
// Node.js环境
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
});
Promise 静态方法
立即返回结果
-
Promise.resolve(value):创建一个立即 resolve 的 Promise:
javascriptlet resolvedPromise = Promise.resolve("立即解析的值");
-
如果接收的是一个 promise ,那么仍然返回该 Promise 对象;如果接收的是一个普通值,那么就将其包装成 Promise 对象。
jsconst originalPromise = new Promise(resolve => resolve('结果')); const wrappedPromise = Promise.resolve(originalPromise); console.log(originalPromise === wrappedPromise); // true,是同一个对象
-
-
Promise.reject(error):创建一个立即 reject 的 Promise:
javascriptlet rejectedPromise = Promise.reject(new Error("立即拒绝的错误"));
Promise.all( iterable )
Promise.all
的核心行为:
-
接收一个可迭代对象(通常是 Promise 数组);该对象内也允许放入非
promise
的"常规"值。 -
返回一个新的 Promise 对象
-
当所有输入的 Promise 都成功时,返回结果数组;结果数组中元素的顺序与其在传入的类数组内顺序相同;
jsfunction timePromise(contain, ms){ return new Promise( resolve => setTimeout(() => resolve(contain), ms) ) } let arr = [ timePromise(1, 3000), timePromise(2, 2000), 3, "4", timePromise(5, 1000), ]; Promise.all(arr).then(alert); // 1,2,3,4,5
-
当任意一个 Promise 失败时,立即拒绝;返回结果变为该失败的 Promise 抛出的错误。
jslet arr = [ Promise.resolve(1), Promise.reject(new Error('失败')), 2, ]; Promise.all(arr).catch(alert); // Error: 失败
手动实现 Promise.all
,其内部逻辑类似下面所写代码:
js
Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
// 如果输入不是可迭代对象,直接reject
if (!promises || typeof promises[Symbol.iterator] !== 'function') {
return reject(new TypeError('Argument is not iterable'));
}
// 如果输入数组为空,直接resolve空数组
if (promises.length === 0) {
return resolve([]);
}
const results = new Array(promises.length);
let completedCount = 0;
promises.forEach((promise, index) => {
// 确保每个元素都是Promise,如果不是则用Promise.resolve包装
Promise.resolve(promise)
.then(result => {
results[index] = result;
completedCount++;
// 当所有Promise都完成时resolve结果数组
if (completedCount === promises.length) {
resolve(results);
}
})
.catch(err => {
// 任何一个Promise失败则立即reject
reject(err);
});
});
});
}
// 可用上文的案例数组进行测试
Promise.allSettled
Promise.allSettled
与 Promise.all
功能类似。但Promise.allSettled
无论结果如何都会返回一个对应的结果数组,数组元素都是对象:
- 成功的响应,结果数组对应元素:
{status:"fulfilled", value:result}
, - 出现 error 的响应,结果数组对应元素:
{status:"rejected", reason:error}
。
js
let arr = [
Promise.resolve(1),
Promise.reject(new Error('失败')),
2,
];
Promise.allSettled(arr).then(console.log).catch(console.log); // catch 不会运行
/* 结果如下:
[
{status: 'fulfilled', value: 1},
{status: 'rejected', reason: Error: 失败 at <anonymous>:3:18},
{status: 'fulfilled', value: 2}
]
可以借助 Promise.all
来手动实现(当然这样操作消耗性能更多一点,可以直接修改上文写的 Promise.all
手动实现):
js
// 定义处理器,接收 Promise 的成功或者失败结果
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
// 定义包装器,将传入数组内的元素全部包装成 Promise,并用 then 处理程序接收他们的结果,用定义的处理器装入相应的对象中
function convertedPromises(promises){
return promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
}
// 用 Promise.all 的形式返回结果
Promise.myAllSettled = function (promises) {
const arr = convertedPromises(promises)
return Promise.all(arr);
};
// 可用上文的案例数组进行测试
Promise.race
Promise.race
与 Promise.all
功能类似。但 Promise.race
只等待第一个完成或出错的 promise 并获取其结果。
js
let arr = [
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
];
Promise.race(arr).then(alert).catch(alert); // 1
手动实现 Promise.race
,代码类似如下所写:
js
// 为简化代码,默认传入的是数组
Promise.myRace = function(promises) {
return new Promise((resolve, reject) => {
if (promisesArray.length === 0) {
return; // 永远pending,与原生行为一致
}
// 确保每个元素都是Promise
for (const promise of promisesArray) {
Promise.resolve(promise).then(resolve, reject); // 第一个完成的会触发外层Promise
}
});
};
Promise.any
Promise.any
则是 Promise.race
修改版。Promise.any
不会抛出错误,只等待第一个完成的 promise 并获取其结果。
js
// 注意,这里是 Error 最先被抛出,但 catch 未被触发
let arr = [
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
];
Promise.any(arr).then(alert).catch(alert); // 1
手动实现 Promise.any
,代码类似如下:
js
// 为简化代码,默认传入的是数组
Promise.myAny = function(promises) {
return new Promise((resolve, reject) => {
if (promisesArray.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
let rejectedCount = 0;
const errors = new Array(promisesArray.length);
promisesArray.forEach((promise, index) => {
Promise.resolve(promise)
.then(resolve) // 任何一个成功就立即解决
.catch(err => {
errors[index] = err;
rejectedCount++;
// 当所有Promise都拒绝时
if (rejectedCount === promisesArray.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
};
超时控制
promise 中没有直接设置"取消"这一功能,需要借助其他方法来实现中止的操作。
AbortController可以帮助我们解决 ,但它不是 Promise API 的一部分。
js
function timeoutPromise(fn, signal) {
return new Promise((resolve, reject) => {
// 如果信号已经中止,立即拒绝
if (signal.aborted) {
return reject(new DOMException('Aborted', 'AbortError'));
}
// 执行异步操作
fn(resolve, reject);
// 监听中止事件
signal.addEventListener('abort', () => {
reject(new DOMException('Aborted', 'AbortError'));
});
});
}
const controller = new AbortController();
const signal = controller.signal;
const task = timeoutPromise( resolve=>setTimeout( ()=>resolve(1), 1000 ) )
task
setTimeout(() => controller.abort(), 500); // 500ms后取消
也可以使用 Promise.race
来控制
javascript
function withTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), timeout)
)
]);
}
Promise.all([
withTimeout(fetch('/api1'), 5000),
withTimeout(fetch('/api2'), 5000)
])
.then(/* ... */);
更新的 Promise 方法
JavaScript 之后在 ES 规范中又提出了一些相对较新的提案。
Promise.withResolvers()
Promise.try()
是 ES2024 新增的 Promise 静态方法,简化了 Promise 的创建和管理,使异步编程更容易理解。
这个方法减少样板代码,避免了手动提取 resolve
/reject
的繁琐过程,代码意图更加清晰明确。同时,解决了在某些情况下 resolve/reject 不在正确作用域的问题。
js
function getWeather(city) {
const { promise, resolve, reject } = Promise.withResolvers();
// 模拟 API 调用
setTimeout(() => {
if (city === "New York") {
resolve("Sunny, 75°F");
} else {
reject("City not found");
}
}, 2000);
return promise;
}
// 使用该函数
getWeather("New York")
.then(weather => console.log("Weather:", weather))
.catch(error => console.log("Error:", error));
Promise.try()
Promise.try()
是 ES2025 新增的 Promise 静态方法,它提供了一种统一处理同步/异步函数的标准化方案。目前(2025-4-25),部分浏览器还未支持。
在实际开发中,我们经常遇到一种情况:有一个函数,我们不知道或不想区分它是同步还是异步,但又想用 Promise 来处理它。
用 Promise.resolve().then(f);
来包装这个函数,则会使其变成异步的,失去同步函数的特性。(同步、异步函数的具体区别在于 Javascript 事件循环特性)
而 Promise.try
让同步函数保持同步特性,在当前事件循环立即执行;异步函数也保持原有异步特性;让同步、异步代码具有统一的错误处理 API 。
Promise.try
与普通的 promise
一样,具有自动捕获同步异常并转为 rejected Promise
的能力,方便链上的错误补抓函数执行;同时避免了金字塔型代码,格式扁平化。
js
// 同步错误自动捕获
Promise.try(() => {
throw new Error('Sync Error')
}).catch(console.log) // 输出错误信息
async 函数
基本使用
Async
/await
是 JavaScript 中处理异步操作的一种更优雅的语法,它建立在 Promise 之上,使异步代码看起来更像同步代码,可读性更高。
使用 async
关键字可以便捷创建一个异步函数。
js
async function func() {/*...*/}
// 类似以下代码
function func(){
return new Promise(function(){
/*...*/
})
}
- 返回值会被包装成一个 Promise;
- 非 Promise 返回值会被自动包装成 resolved Promise;
在 async
函数内使用关键字 await
,可让异步函数阻塞,直到 promise 完成,并将结果值返回。
js
async function f() {
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // 等待直到 promise 完成
console.log(result); // "done!"
}
- 只能在
async
函数内部使用,阻塞async
函数; - 不会阻塞主线程,JavaScript 引擎可以处理其他任务;
- 使用
await
后返回结果值,不再返回一个 Promise,无需.then
方法。
错误处理
可以使用 try
/catch
捕获错误:
javascript
async function f() {
try {
let response = await fetch('http://no-such-url');
let data = await response.json();
} catch(err) {
console.error("请求失败:", err);
}
}
若没有 try..catch
,那么调用生成的 promise
将变为 rejected
。可在函数调用后面添加 .catch
来处理这个 error:
javascript
f().catch(alert); // 处理未捕获的错误
若不添加 .catch
,则需全局监听unhandledrejection
事件,处理promise
抛出的错误:
js
// 浏览器环境
window.addEventListener('unhandledrejection', event => {
console.error('未处理的Promise错误:', event.reason);
event.preventDefault(); // 阻止默认错误输出
});
其他用法
-
顶层 await(仅在模块中):
现代浏览器在
modules
里允许顶层的await
,若没有使用 modules,或者必须兼容旧版本浏览器,那么可以包装到匿名的异步函数中:
js(async () => { let response = await fetch('/article/promise-chaining/user.json'); let user = await response.json(); // ... })();
-
处理 thenable 对象:
await
允许使用thenable
对象(兼容 promise 的对象,具有可调用的.then
方法)jsclass Thenable { constructor(num) { this.num = num; } then(resolve, reject) { setTimeout(() => resolve(this.num * 2), 1000); } } async function f() { let result = await new Thenable(1); console.log(result); // 2 }
-
类中的 async 方法:
要声明一个 class 中的 async 方法,只需在对应方法前面加上 async 即可。
jsclass Waiter { async wait() { return await Promise.resolve(1); } } new Waiter().wait().then(console.log); // 1
-
支持 Promise 的静态方法:
js// 并行处理多个 Promise let results = await Promise.all([ fetch(url1), fetch(url2), fetch(url3) ]);