Promise:解决JavaScript异步操作
Promise 的出现主要是为了解决 JavaScript 中异步操作的处理问题。在传统的 JavaScript 中,异步操作通常使用回调函数来处理,但这种方式会导致代码嵌套层级过深,可读性差,而且容易产生回调地狱(callback hell)的情况,降低了代码的可维护性和可理解性。
简单的说法:它就像是一种特殊的承诺,用来解决 JavaScript 中的异步问题, 想象一下你承诺要跟你朋友五一去北京玩,但是时间还没到。在等待这个过程完成之前,你可以做其他事情,一旦约定的时间到了,你就要履行承诺,如果你去了就履行,但是你也有权利不履行。
Promise解决什么问题
回调地狱:如果有多个异步操作依赖于前一个操作的结果,代码就会出现多层嵌套的回调,造成代码结构混乱难以维护。
asyncOperation1(callback: () => void) {
setTimeout(function () {
console.log('异步操作1完成');
callback();
}, 1000);
}
asyncOperation2(callback: () => void) {
setTimeout(function () {
console.log('异步操作2完成');
callback();
}, 1500);
}
asyncOperation3(callback: () => void) {
setTimeout(function () {
console.log('异步操作3完成');
callback();
}, 2000);
}
ngOnInit(): void {
console.log('开始');
this.asyncOperation1(() => {
this.asyncOperation2(() => {
this.asyncOperation3(() => {
console.log('所有异步操作完成');
});
});
});
}
开始 异步操作1完成 异步操作2完成 异步操作3完成 所有异步操作完成
错误处理困难:错误处理通常依赖于回调函数的调用方式,容易出现漏处理或混乱的情况。
asyncOperation(callback: (error: any, result?: number) => void) {
setTimeout( () => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
callback(null, randomNumber);
} else {
callback(new Error('异步操作失败'));
}
}, 1000);
}
this.asyncOperation((error: any, result?: number) => {
if (error) {
console.error('异步操作失败:', error.message);
// 漏处理错误,没有提供错误处理逻辑
} else {
console.log('异步操作成功,结果为:', result);
// 只处理了成功情况,没有考虑错误处理的可能性
}
});
小于0.5 异步操作失败: 异步操作失败
大于0.5 异步操作成功: 0.52312344 (随机)
Promise基本使用
Promise 对象通过自身的状态,来控制异步操作。Promise 实例具有三种状态。
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled):意味着操作成功完成。
- 已拒绝(rejected):意味着操作失败。
上面三种状态里面,fulfilled和rejected合在一起称为resolved(已定型)。
状态的变化只有2种
- 从“未完成”到“成功”
- 从“未完成”到“失败”
状态一旦发生变化,就不会再发生变化,因此,Promise只有2种结果
- 异步操作成功:从pending到fulfilled
- 异步操作失败:从pending到rejected
Promise 构造函数
Promise 构造函数是用来创建 Promise 实例的基础。它接受一个带有两个参数的函数作为参数。这个函数会立即执行,通常包含异步操作。这两个参数一般被称为 resolve 和 reject,它们是 JavaScript 引擎提供的回调函数。resolve 函数用于表示异步操作成功并返回结果,而 reject 函数用于表示异步操作失败并返回一个错误对象。
const promise = new Promise((resolve, reject) => {
const randomNumber = Math.random();
if (randomNumber < 0.5){
resolve(randomNumber);
} else { /* 异步操作失败 */
reject(new Error(异步操作失败));
}
});
Promise 的链式调用
.then() 方法最多接受两个参数;第一个参数是 Promise 兑现时的回调函数,第二个参数是 Promise 拒绝时的回调函数。每个 .then() 返回一个新生成的 Promise 对象,这个对象可被用于链式调用,
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("yunzhi");
}, 300);
});
promise
.then((value) => {
console.log(value, '异步操作')
}, (value) => {
console.log(value, '操作失败')
});
// yunzhi 操作成功
promise
.then((value) => {
console.log(value, '操作成功');
return value + '.club';
}, (value) => {
console.log(value, '操作失败')
}).then(value => {
console.log(value, '操作成功');
});
// yunzhi 操作成功
// yunzhi.club 操作成功
解决回调地狱
Promise 的链式调用可以使代码更加扁平化、易于理解,并且避免了多层嵌套的回调函数,从而解决了回调地狱问题。
asyncOperation1(callback: () => void) {
setTimeout(function () {
console.log('异步操作1完成');
callback();
}, 1000);
}
asyncOperation2(callback: () => void) {
setTimeout(function () {
console.log('异步操作2完成');
callback();
}, 1500);
}
asyncOperation3(callback: () => void) {
setTimeout(function () {
console.log('异步操作3完成');
callback();
}, 2000);
}
asyncOperation1()
.then(() => {
return asyncOperation2();
})
.then(() => {
return asyncOperation3();
})
.then(() => {
console.log('所有异步操作完成');
});
开始 异步操作1完成 异步操作2完成 异步操作3完成 所有异步操作完成
Promise 的链式调用和 .catch() 方法来更可靠地处理错误,漏处理或混乱的情况
this.asyncOperation().then((result) => {
console.log('异步操作成功,结果为:', result);
}).catch((error) => {
console.error('异步操作失败:', error.message);
// 在 .catch() 中处理异步操作失败的情况
})
asyncOperation() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber < 0.5) {
resolve(randomNumber);
}
reject(new Error('异步操作1失败'));
}
, 1000)
})
}
小于0.5 异步操作失败: 异步操作失败
大于0.5 异步操作成功: 0.52312344 (随机)
总结
通过 Promise 构造函数,我们可以更加灵活地处理异步操作,并且避免了传统回调函数所带来的回调地狱问题。更好的错误处理: Promise 提供了统一的错误处理机制,使得错误更容易捕获和处理。通过 .catch() 方法,可以捕获到 Promise 链中的任何一个 Promise 对象的错误。
同步和异步的区别
同步操作
同步操作是按照代码的顺序执行的。当你执行一个同步操作时,JavaScript 引擎会阻塞代码的执行,直到这个操作完成,然后才会继续往下执行。
console.log("开始");
console.log("yunzhi");
console.log("结束");
// 开始
// yunzhi
// 结束
异步操作:
异步操作允许代码在执行某些操作时继续往下执行,而不需要等待这些操作完成。当操作完成后,通常会执行一个回调函数来处理结果。
JavaScript 中常见的异步操作包括定时器函数(如 setTimeout、setInterval)、事件监听(如点击事件、网络请求完成事件)、Promise 等。
console.log("开始");
setTimeout(() => {
console.log("yunzhi");
}, 2000); // 2秒后执行
console.log("结束");
// 开始
// 结束
// yunzhi
异步操作的原理:
JavaScript 是单线程执行的,意味着一次只能执行一个任务。当遇到异步操作时,JavaScript 将这些操作交给浏览器的其他线程(比如定时器线程、网络请求线程等)来处理,自己则继续执行后续的代码。当异步操作完成后,会将对应的回调函数加入任务队列(task queue)中。一旦主线程空闲,事件循环(event loop)就会检查任务队列,如果有任务,就将其取出并执行。
GUI 渲染线程:
主要负责页面的渲染,解析 HTML,CSS,构建 DOM 树,布局和绘制等。
JS 引擎线程:
这个线程就是负责执行JS的主线程主要负责处理 JavaScript 脚本,"JS是单线程的"就是指的这个线程, 也主要负责执行准备好的待执行的事件,即定时器结束,或者异步请求成功并正确返回时,将依次进入任务 队列,等待 JS 引擎线程的执行。
定时触发线程:
前面异步例子的setTimeout其实就运行在这里,他跟JS主线程根本不在同一个地方,所以“单线程的JS”能够实现异步。JS的定时器方法还有setInterval,也是在这个线程。
事件触发线程:
事件触发线程是指浏览器中的一个特殊线程,它负责监听和处理用户的交互事件,例如点击、滚动、键等。当这些事件发生时,事件触发线程会将事件加入到任务队列中,并通知主线程来处理这些事件。
异步HTTP请求线程:
这个线程负责处理异步的ajax请求,当请求完成后,他也会通知事件触发线程,然后事件触发线程将这个事件放入任务队列给主线程执行。
事件循环:
事件循环(Event Loop)是 JavaScript 运行时环境中一种重要的机制,它负责管理和协调代码的执行顺序,确保异步任务能够按照预期顺利执行,并且保证了 JavaScript 单线程的特性。
console.log("开始");
setTimeout(() => {
console.log("yunzhi");
}, 0);
Promise.resolve()
.then(() => console.log("结束"));
console.log("测试");
// 开始
// 测试
// 结束
// yunzhi
参考文章
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Refer...
https://www.runoob.com/js/js-promise.html
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。