一篇文章学会ES6 Promise
ES6 Promise
详解
一、JavaScript
中实现异步的方式总结
JavaScript 的异步操作实现机制主要包括以下几种技术和模式:
- 回调函数 (Callbacks)
- Promises
- async/await
下面是对每种技术的详细解释:
1. 回调函数 (Callbacks)
回调函数是最基本的异步处理机制。你将一个函数作为参数传递给另一个函数,并在异步操作完成时调用这个回调函数。这种方法虽然简单,但可能会导致所谓的“回调地狱”——即多个回调嵌套在一起,使得代码难以维护。
示例:
function fetchData(callback) {
setTimeout(() => {
const data = "Data fetched";
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data); // 输出: Data fetched
});
2. Promises
Promise
是一种更现代的处理异步操作的方式,它提供了更好的链式调用和错误处理机制。Promise
代表一个可能在未来某个时间点完成(或失败)的操作的结果。
示例:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = "Data fetched";
resolve(data);
}, 1000);
});
}
fetchData().then((data) => {
console.log(data); // 输出: Data fetched
}).catch((error) => {
console.error("Error:", error);
});
解释:
Promise
有三种状态:pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。resolve
用于将Promise
的状态从pending
转为fulfilled
。reject
用于将Promise
的状态从pending
转为rejected
。.then()
用于处理成功的结果,.catch()
用于处理错误。
3. async/await
async/await
是基于 Promises
的语法糖,使异步代码看起来像同步代码,从而提高代码的可读性和可维护性。
示例:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = "Data fetched";
resolve(data);
}, 1000);
});
}
async function getData() {
try {
const data = await fetchData();
console.log(data); // 输出: Data fetched
} catch (error) {
console.error("Error:", error);
}
}
getData();
解释:
async
函数总是返回一个Promise
。await
用于等待Promise
完成,await
表达式可以在async
函数内部使用。- 使用
try/catch
语句处理await
表达式可能抛出的错误。
事件循环 (Event Loop)
JavaScript 是单线程的,所有的异步操作都是通过事件循环机制实现的。事件循环允许 JavaScript 处理异步操作,同时保持主线程的顺畅执行。基本的事件循环工作流程如下:
- 执行同步代码(从执行栈中)。
- 将异步操作(如
setTimeout
、Promise
)注册到任务队列。 - 当执行栈为空时,事件循环从任务队列中取出任务,并执行它们。
示例:
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
}, 0);
Promise.resolve().then(() => {
console.log("Promise 1");
});
console.log("End");
解释:
- 同步代码
console.log("Start")
和console.log("End")
先执行。 setTimeout
和Promise
的回调函数分别被加入到任务队列中。- 当执行栈为空时,事件循环会处理任务队列中的回调函数。由于
Promise
回调的优先级高于setTimeout
,所以Promise 1
会先于Timeout 1
输出。
总结
JavaScript 通过回调函数、Promises 和 async/await
提供了不同的异步处理机制,这些机制都依赖于事件循环来协调异步操作的执行。这些工具和技术让你能够编写更为流畅的异步代码,从而有效地处理复杂的应用逻辑。
二、Promise
基本使用介绍
当然!在 TypeScript 中,Promise
的使用可以涉及多个方面,包括基础用法、类型系统、异步操作的错误处理以及高级用法等。下面我会详细讲解这些内容。
1. Promise
的基本概念
Promise
是一种用于处理异步操作的对象。它代表了一个在未来可能完成的操作及其结果。基本的 Promise
状态有:
- Pending(进行中): 初始状态,操作尚未完成。
- Fulfilled(已成功): 操作完成,结果成功。
- Rejected(已失败): 操作完成,结果失败。
2. 创建和使用 Promise
创建 Promise
一个 Promise
对象由一个执行器函数(executor function)初始化,该函数接受两个参数:resolve
和 reject
。
const promise = new Promise<number>((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true; // 假设这是某个条件的结果
if (success) {
resolve(42); // 成功,返回值为 42
} else {
reject(new Error('Something went wrong')); // 失败,返回错误
}
}, 1000);
});
使用 Promise
可以使用 then
和 catch
方法来处理 Promise
的结果或错误。
promise
.then(result => {
console.log('Result:', result); // Output: Result: 42
})
.catch(error => {
console.error('Error:', error.message);
});
3. TypeScript 的类型系统与 Promise
TypeScript 允许你指定 Promise
的类型,这样可以让你在编译时得到类型检查。
function fetchNumber(): Promise<number> {
return new Promise<number>((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 1000);
});
}
在这个例子中,fetchNumber
函数返回一个 Promise<number>
。这意味着当 Promise
完成时,它将返回一个 number
类型的结果。
4. async
和 await
async
和 await
是处理 Promise
的语法糖,使得异步代码看起来像是同步的。在JavaScript中,async和await是用于处理异步操作的关键字。它们通常一起使用,以简化异步代码的编写和读取。
async
函数
async
关键字用于定义一个异步函数,异步函数总是返回一个 Promise
对象。即使你在 async
函数中返回一个普通值,它也会被包装成一个 Promise
。在async函数内部,可以使用await关键字等待Promise的完成。
async function getNumber(): Promise<number> {
return 123; // 实际上返回的是 Promise.resolve(123)
}
await
关键字
await
关键字只能在 async
定义的异步函数中使用。它会暂停 async
函数的执行,直到 Promise
被解决或拒绝。await
表达式的值就是Promise resolve
的结果。
async function main() {
try {
const number = await getNumber();
console.log(number); // Output: 123
} catch (error) {
console.error('Error:', error);
}
}
main();
5. 异常处理
在异步代码中处理错误非常重要。你可以使用 try
/catch
语句来捕获 async
函数中的错误,或者在 Promise
链中使用 catch
方法。
async function fetchData(): Promise<number> {
return new Promise<number>((resolve, reject) => {
// 模拟错误
setTimeout(() => reject(new Error('Failed to fetch data')), 1000);
});
}
async function main() {
try {
const data = await fetchData();
console.log(data);
// 这里的data是一个`Promise`对象
} catch (error) {
console.error('Error:', error.message); // Output: Error: Failed to fetch data
}
}
main();
使用Promose
链中的catch
方法捕获错误信息
async function fetchData():Promise<number>{
return new Promise<number>((resolve,reject) => {
setTimeout(() => reject(new Error('Failed to fetch data')), 1000);
})
}
async function main(){
featchData().then(res=>{
// 这里的`res`resolve中返回的参数
console.log(res);
}).catch((e)=>{
console.log("error",e);
})
}
6. Promise.all 和 Promise.race
有时你可能需要处理多个 Promise
。Promise.all
和 Promise.race
是两个有用的方法。
Promise.all
: 接受一个Promise
数组,当所有Promise
都完成时,它返回一个新的Promise
,其结果是所有Promise
的结果组成的数组。如果任何一个Promise
失败,则返回的Promise
也会失败。
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(results => {
console.log(results); // Output: [1, 2, 3]
})
.catch(error => {
console.error('Error:', error);
});
Promise.race
: 接受一个Promise
数组,返回一个新的Promise
,该Promise
会在第一个Promise
完成或失败时完成。
const p1 = new Promise<number>((resolve) => setTimeout(() => resolve(1), 500));
const p2 = new Promise<number>((resolve) => setTimeout(() => resolve(2), 100));
Promise.race([p1, p2])
.then(result => {
console.log(result); // Output: 2 (因为 p2 先完成)
});
7. 其他高级用法
Promise.allSettled
Promise.allSettled
接受一个 Promise
数组,返回一个新的 Promise
,其结果是每个 Promise
的状态和结果组成的数组。
const p1 = Promise.resolve(1);
const p2 = Promise.reject(new Error('Failed'));
const p3 = Promise.resolve(3);
Promise.allSettled([p1, p2, p3])
.then(results => {
console.log(results);
/*
Output:
[
{ status: 'fulfilled', value: 1 },
{ status: 'rejected', reason: Error: Failed },
{ status: 'fulfilled', value: 3 }
]
*/
});
三、实际应用场景
1. 在请求接口中使用Promise
axios
是一个流行的 HTTP 客户端库,它基于 Promise
,使得处理异步 HTTP 请求变得简单。你可以利用 axios
发起异步请求,并结合 Promise
的特性来处理请求的结果。以下是一些示例,展示如何在 axios
中使用 Promise
进行异步操作。
const axios = require('axios');
axios.post('https://api.example.com/data', { key: 'value' })
.then(response => {
console.log('Data:', response.data); // 处理响应数据
})
.catch(error => {
console.error('Error:', error); // 处理错误
});
2. 在需要等待执行的函数中使用Promsie
import JSZip from 'jszip'
import shp from 'shpjs'
const parseZip = async(zip)=>{
const jsZip = new JSZip() // 解析zip数据为二进制数据
const zipData = await jsZip.loadAsync(zip)
const data = await zipData.generateAsync({ type: 'arraybuffer' }) // 将zip文件转化为二进制流
return await shp(data) // 将二进制流转换为geojson数据
}
3. 循环中包含异步操作处理
在JavaScript中,循环的执行是同步的,这意味着一旦进入循环,它会逐次执行每个迭代,直到完成。因此,在循环完成之前,后续代码是不会被执行的。然而,如果你的循环中包含了异步操作(如网络请求、文件读写等),你可能需要使用异步机制来确保循环中的所有异步操作完成后再执行后续代码。
使用同步循环(简单示例)
如果你的循环是同步的(即没有异步操作),你不需要额外处理,只需确保循环完成后,代码会继续执行:
// 示例:同步循环
for (let i = 0; i < 10000; i++) {
// 进行一些同步操作
console.log(i);
}
// 循环结束后执行的代码
console.log("Loop finished.");
使用异步操作(例如异步请求)
如果你的循环中包含了异步操作,你可以使用 Promises 和 async/await 来确保在所有异步操作完成后再执行后续代码。以下是一个示例,展示了如何处理包含异步操作的循环:
// 模拟异步操作的函数
function asyncOperation(index) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Operation ${index} completed.`);
resolve();
}, Math.random() * 1000); // 随机延迟
});
}
// 使用 async/await 确保循环中的所有异步操作完成
async function runAsyncLoop() {
const promises = [];
for (let i = 0; i < 10000; i++) {
promises.push(asyncOperation(i)); // 将每个异步操作的 Promise 添加到数组中
}
await Promise.all(promises); // 等待所有异步操作完成
// 循环完成后执行的代码
console.log("All operations finished.");
}
runAsyncLoop();
解释:
asyncOperation
是一个模拟异步操作的函数,返回一个Promise
。- 在
runAsyncLoop
函数中,我们将每个异步操作的Promise
存储在promises
数组中。 Promise.all(promises)
等待所有的Promise
完成。- 在所有异步操作完成后,执行
console.log("All operations finished.");
。
4. Promise.all
实际应用场景
有一个人员列表,每个人员都有一个定位信息,需要通过调用高德导航接口查询出每个人员到目的地的距离和时间,然后根据距离和时间对人员列表排序。
代码实现过程:
// 传入人员列表数据
async handleData(data) {
return new Promise(async (resolve,reject)=>{
let hasLngLatItems = [];
let allRequest = [];
try{
data.forEach((item,index) => {
// 只请求有位置的数据
if(item.isonline == 1){
hasLngLatItems.push(index)
let url = "https://restapi.amap.com/v3/direction/driving?key=" + appConfig.gaodeApi + "&origin=" + this.fireLngLat[0] + "," + this.fireLngLat[1] + "&destination=" + item.lng + "," + item.lat + "&extensions=base";
let request = axios({method: 'get',url: url})
allRequest.push(request);
}
})
// 所有请求完成 再执行排序
let responses = await Promise.all(allRequest);
// 将信息补充道数组中
responses.forEach((res,idx)=>{
if (res.data.route && res.data.route.paths && res.data.route.paths[0]) {
let obj = res.data.route.paths[0]
data[hasLngLatItems[idx]].distance = (obj.distance / 1000).toFixed(2)
data[hasLngLatItems[idx]].duration = Math.round(obj.duration / 60)
}
})
resolve(data);
}catch(e){
console.log(e);
reject(null)
}
})
}
// 调用查询距离接口,对返回的结果进行排序
this.handleData(rel).then(dl => {
if(dl){
// _this.listData = dl
// _this.allData = dl
dl.forEach((item,index)=>{
console.log("typeof",typeof dl[index].distance)
if(typeof dl[index].distance != "undefined"){
dl[index].distance = Number(dl[index].distance);
}else{
dl[index].distance = null;
}
})
// 进行综合排序
dl.sort((a,b) =>
{
if (a.distance === null && b.distance === null) return 0; // 两者都是 null,保持原有顺序
if (a.distance === null) return 1; // a 是 null,排到后面
if (b.distance === null) return -1; // b 是 null,排到后面
if (a.isonline !== b.isonline) {
return (a.isonline === b.isonline) ? 0 : a.isonline ? -1 : 1; // 在线用户优先
}
return a.distance - b.distance; // 对其他元素进行升序排序
}
)
// 获取到全部的排序数据
_this.listData = dl;
_this.allData = dl;
// 根据距离筛选
setTimeout(() => {
_this.radioChange()
}, 1000)
}
})
三、 总结
TypeScript 中的 Promise
提供了一种强大的方式来处理异步操作。通过利用 TypeScript 的类型系统和 async
/await
语法,可以使异步代码更易于理解和维护。掌握 Promise
的各种方法和用法将帮助你编写更健壮和可维护的异步代码。如果你有更多问题或需要深入探讨,请告诉我!