for 循环里面的 异步 处理
-
for
循环- 基本原理:
for
循环是最基础的循环结构,它有一个初始化表达式、一个条件表达式和一个更新表达式。语法为for (初始化; 条件; 更新) {循环体}
。例如:for (var i = 0; i < 5; i++) { console.log(i); }
- 它会按照顺序依次执行循环体,在每次循环开始时检查条件表达式是否为真,执行完循环体后,执行更新表达式。
- 异步操作问题:
- 在
for
循环中直接处理异步操作会出现问题。因为for
循环不会等待异步操作完成就会继续下一次循环。例如:for (let i = 0; i < 3; i++) { setTimeout(() => { console.log(i); }, 1000); }
- 上述代码会在1秒后同时输出
3
个数字(在JavaScript中,很可能是3
、3
、3
),因为setTimeout
是异步操作,for
循环不会等待setTimeout
中的回调函数执行就会继续下一次循环,当所有setTimeout
的回调函数执行时,i
的值已经变成了3
。
- 在
- 解决方法(使用
async/await
结合Promise
):- 如果要在
for
循环中等待异步操作完成,可以将异步操作封装在Promise
中,然后在async
函数中使用await
。例如,假设有一个返回Promise
的异步函数fetchData
:async function loopWithAsync() { for (let i = 0; i < 3; i++) { await new Promise((resolve) => { setTimeout(() => { console.log(i); resolve(); }, 1000); }); } } loopWithAsync();
- 这样就可以实现每次等待
setTimeout
中的异步操作完成后,再进行下一次循环。
- 如果要在
- 基本原理:
-
for...in
循环- 基本原理:
for...in
循环主要用于遍历对象的可枚举属性(包括继承的可枚举属性)。语法为for (变量 in 对象) {循环体}
。例如:const obj = {a: 1, b: 2, c: 3}; for (let key in obj) { console.log(key + ': ' + obj[key]); }
- 它会将对象的属性名(键)赋值给变量,然后在循环体中可以通过对象[变量]的方式访问属性值。
- 异步操作问题和解决方法:
- 与
for
循环类似,for...in
循环本身不会等待异步操作。如果要在for...in
循环中等待异步操作,也可以使用async/await
结合Promise
的方式。例如,假设对象的属性值是通过异步操作获取的:const asyncObj = { a: () => new Promise((resolve) => setTimeout( () => resolve(1), 1000) ), b: () => new Promise((resolve) => setTimeout( () => resolve(2), 2000) ), c: () => new Promise((resolve) => setTimeout( () => resolve(3), 3000) ) }; async function loopForInWithAsync() { for (let key in asyncObj) { let value = await asyncObj[key](); console.log(key + ': ' + value); } } loopForInWithAsync();
- 这样就可以在每次获取对象属性对应的异步值后,再进行下一个属性的处理。
- 与
- 基本原理:
-
for...of
循环- 基本原理:
for...of
循环用于遍历可迭代对象(如数组、字符串、Set
、Map
等)的值。语法为for (变量 of 可迭代对象) {循环体}
。例如:const arr = [1, 2, 3]; for (let value of arr) { console.log(value); }
- 它会将可迭代对象中的每个元素依次赋值给变量,然后执行循环体。
- 异步操作问题和解决方法:
- 同样,
for...of
循环本身不等待异步操作。如果在遍历的可迭代对象中涉及异步操作获取元素的值,也可以使用async/await
结合Promise
来处理。例如,假设有一个数组,其中的元素需要通过异步操作获取:const asyncArray = [ () => new Promise((resolve) => setTimeout(() => resolve(1), 1000)), () => new Promise((resolve) => setTimeout(() => resolve(2), 2000)), () => new Promise((resolve) => setTimeout(() => resolve(3), 3000)) ]; async function loopForOfWithAsync() { for (let func of asyncArray) { let value = await func(); console.log(value); } } loopForOfWithAsync();
- 这样就可以逐个等待异步操作获取元素的值,再进行下一个元素的处理。
- 同样,
- 基本原理:
区别总结:
for
循环:主要用于固定次数的循环,通过初始化、条件判断和更新操作来控制循环流程,适用于遍历数组索引等情况。在处理异步操作时需要额外注意等待异步完成的机制。for...in
循环:侧重于遍历对象的属性(键),包括继承的可枚举属性。在处理对象属性相关的异步操作时可以使用async/await
来等待异步操作完成。for...of
循环:用于遍历可迭代对象的值,如数组、字符串等。当可迭代对象中的元素获取涉及异步操作时,也可以通过async/await
来正确处理异步等待。
symbol
- 基本概念
Symbol
是ES6引入的一种原始数据类型,表示独一无二的值。它的主要特点是其值具有唯一性,即使创建了相同描述的Symbol
,它们的值也不相同。例如:let sym1 = Symbol('description'); let sym2 = Symbol('description'); console.log(sym1 === sym2); // false
- 应用实例
- 作为对象属性名
- 由于
Symbol
值的唯一性,它可以用来作为对象的属性名,防止属性名冲突。例如,假设有一个对象,其中有一些内部使用的属性,不想被外部代码意外访问或覆盖:let myObject = {}; let internalProp = Symbol('internal property'); myObject[internalProp] = 'This is an internal value'; console.log(myObject[internalProp]); // This is an internal value for (let key in myObject) { // 使用for...in循环不会遍历到Symbol属性 console.log(key); }
- 在这个例子中,
internalProp
作为一个Symbol
类型的属性名,不会被常规的for...in
循环遍历到,这样就可以有效地隐藏对象的内部属性。
- 由于
- 实现私有属性(某种程度上)
- 在JavaScript类中,可以利用
Symbol
来模拟私有属性。例如:class MyClass { constructor() { this[Symbol('privateProp')] = 'private value'; } getPrivateProp() { return this[Symbol('privateProp')]; } } let myInstance = new MyClass(); console.log(myInstance.getPrivateProp()); // 外部无法直接访问Symbol('privateProp')属性
- 这里通过
Symbol
定义的属性在类的外部不能直接访问,只能通过类内部定义的方法来访问,实现了一定程度的属性私有性。
- 在JavaScript类中,可以利用
- 作为常量枚举
Symbol
可以用于定义一组唯一的常量,类似于枚举类型。例如,定义一个表示不同操作类型的符号集合:const OPERATION_ADD = Symbol('add'); const OPERATION_SUBTRACT = Symbol('subtract'); const OPERATION_MULTIPLY = Symbol('multiply'); function calculate(num1, num2, operation) { if (operation === OPERATION_ADD) { return num1 + num2; } else if (operation === OPERATION_SUBTRACT) { return num1 - num2; } else if (operation === OPERATION_MULTIPLY) { return num1 * num2; } } console.log(calculate(2, 3, OPERATION_ADD)); // 5
- 作为对象属性名
bigInt
-
基本概念
BigInt
是JavaScript中的一种数据类型,用于表示任意精度的整数。在JavaScript中,Number
类型有安全范围限制(-9007199254740991
到9007199254740991
,即Number.MIN_SAFE_INTEGER
到Number.MAX_SAFE_INTEGER
),超出这个范围的整数计算可能会出现精度丢失的情况。而BigInt
可以处理任意大小的整数。
-
应用实例
- 大整数运算
- 进行非常大的整数加法运算。例如,计算两个超过
Number
类型安全范围的整数相加:const num1 = BigInt("9007199254740992"); const num2 = BigInt("9007199254740992"); const result = num1 + num2; console.log(result);
- 在这里,
num1
和num2
是BigInt
类型的整数,通过+
运算符进行加法运算,得到正确的结果,不会出现Number
类型可能出现的精度丢失问题。
- 进行非常大的整数加法运算。例如,计算两个超过
- 处理数据库中的大整数ID
- 假设在一个数据库应用中,有一些表使用非常大的整数作为主键,如雪花算法(Snowflake)生成的唯一ID。雪花算法生成的ID是一个64位的整数,在JavaScript中可能会超出
Number
类型的安全范围。// 假设从数据库中获取的雪花ID(这只是示例,实际应用中会从数据库查询等方式获取) const snowflakeId1 = BigInt("1234567890123456789"); const snowflakeId2 = BigInt("1234567890123456790"); function compareIds(id1, id2) { if (id1 > id2) { return 1; } else if (id1 < id2) { return -1; } else { return 0; } } console.log(compareIds(snowflakeId1, snowflakeId2));
- 这里使用
BigInt
类型来正确地比较两个可能超出Number
安全范围的数据库ID的大小。
- 假设在一个数据库应用中,有一些表使用非常大的整数作为主键,如雪花算法(Snowflake)生成的唯一ID。雪花算法生成的ID是一个64位的整数,在JavaScript中可能会超出
- 密码学应用(简单示例)
- 在一些简单的密码学概念验证示例中,可能会涉及到非常大的整数计算。例如,简单的RSA加密算法(这只是一个非常简化的示例,实际的RSA加密要复杂得多)的密钥生成可能会涉及到很大的质数相乘。
// 简单的两个大质数相乘(假设这是密钥生成的一部分) const prime1 = BigInt("123456789012345678901234567890123456789"); const prime2 = BigInt("987654321098765432109876543210987654321"); const publicKeyPart = prime1 * prime2; console.log(publicKeyPart);
- 这个例子展示了如何使用
BigInt
来处理密码学应用中可能出现的大整数乘法运算,以确保计算的准确性。
- 在一些简单的密码学概念验证示例中,可能会涉及到非常大的整数计算。例如,简单的RSA加密算法(这只是一个非常简化的示例,实际的RSA加密要复杂得多)的密钥生成可能会涉及到很大的质数相乘。
- 大整数运算
isNaN
-
基本介绍
- 在JavaScript中,
Number.isNaN()
是一个用于判断一个值是否为NaN
(Not - a - Number)的方法。它是Number
对象的一个静态方法。
- 在JavaScript中,
-
语法和参数
- 语法:
Number.isNaN(value)
。 - 参数
value
:要被检测的值,可以是任何类型的值。
- 语法:
-
应用实例
- 简单数据类型判断
- 例如,判断一个变量是否为
NaN
:let num1 = NaN; let num2 = 5; console.log(Number.isNaN(num1)); // true console.log(Number.isNaN(num2)); // false
- 在这个例子中,
Number.isNaN(num1)
返回true
,因为num1
的值是NaN
,而Number.isNaN(num2)
返回false
,因为num2
是一个正常的数字5
。
- 例如,判断一个变量是否为
- 复杂运算结果判断
- 在一些数学运算中,可能会出现
NaN
的结果,如0
除以0
或者对负数开平方根(在JavaScript的默认数学运算中)等情况。可以使用Number.isNaN
来检查这些运算的结果:let result1 = 0 / 0; console.log(Number.isNaN(result1)); // true let result2 = Math.sqrt(-1); console.log(Number.isNaN(result2)); // true
- 这里
0/0
的结果是NaN
,Math.sqrt(-1)
(在实数范围内)的结果也是NaN
,Number.isNaN
能够准确地检测出这些NaN
值。
- 在一些数学运算中,可能会出现
- 函数参数验证
- 在一个函数中,如果期望接收一个有效的数字参数,而不是
NaN
,可以使用Number.isNaN
来进行验证。例如:function processNumber(num) { if (Number.isNaN(num)) { console.log("参数不能是NaN"); return; } console.log("接收到的有效数字是: " + num); } processNumber(3.14); processNumber(NaN);
- 这个函数
processNumber
首先检查传入的参数是否为NaN
,如果是,则输出提示信息并返回;如果不是,则输出接收到的有效数字。
- 在一个函数中,如果期望接收一个有效的数字参数,而不是
- 简单数据类型判断
-
与全局
isNaN
函数的区别- 全局的
isNaN()
函数会先尝试将传入的值转换为数字,然后再判断是否为NaN
。而Number.isNaN()
不会进行这种类型转换。例如:console.log(isNaN("abc")); // true console.log(Number.isNaN("abc")); // false
- 对于
isNaN("abc")
,它首先会尝试将"abc"
转换为数字,由于转换失败得到NaN
,所以返回true
。而Number.isNaN("abc")
直接判断"abc"
不是NaN
类型,所以返回false
。
- 对于
- 全局的
特殊符号的特殊意义
?.
(可选链操作符)- 基本概念
- 可选链操作符允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。在以前,如果要访问一个嵌套多层的对象属性,需要进行多次条件判断来防止出现
undefined
或null
引用错误。
- 可选链操作符允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。在以前,如果要访问一个嵌套多层的对象属性,需要进行多次条件判断来防止出现
- 语法和应用实例
- 语法:
obj?.prop
、obj?.[expr]
、func?.(...args)
。 - 例如,有一个可能为
null
或undefined
的对象,想访问它内部的属性:const user = { address: { street: '123 Main St' } }; const street = user?.address?.street; console.log(street); // 123 Main St const user2 = null; const street2 = user2?.address?.street; console.log(street2); // undefined
- 在这个例子中,第一个
user
对象正常访问了street
属性,而对于user2
为null
的情况,使用可选链操作符不会抛出错误,而是返回undefined
。
- 语法:
- 基本概念
??
(空值合并操作符)- 基本概念
- 空值合并操作符用于在
null
或undefined
时提供一个默认值。它是一个逻辑操作符,当左侧的值为null
或undefined
时,返回右侧的值。
- 空值合并操作符用于在
- 语法和应用实例
- 语法:
leftValue?? rightValue
。 - 例如,设置一个默认值:
const value1 = null; const defaultValue1 = value1?? 'default'; console.log(defaultValue1); // default const value2 = 0; const defaultValue2 = value2?? 'default'; console.log(defaultValue2); // 0
- 这里
value1
为null
,所以defaultValue1
返回default
,而value2
为0
,不是null
或undefined
,所以defaultValue2
返回0
。
- 语法:
- 基本概念
||
(逻辑或)和??
的区别||
操作符在左侧值为假值(如0
、false
、""
、null
、undefined
)时会返回右侧的值。而??
操作符只有在左侧值为null
或undefined
时才返回右侧的值。例如:const falseValue = 0; const defaultValueWithOr = falseValue || 'default'; const defaultValueWithNullishCoalescing = falseValue?? 'default'; console.log(defaultValueWithOr); // default console.log(defaultValueWithNullishCoalescing); // 0
#
(私有字段)- 基本概念
- 在类中可以使用
#
来定义私有字段。私有字段只能在类的内部访问,外部无法访问这些字段,这有助于更好地封装和保护类的内部状态。
- 在类中可以使用
- 语法和应用实例
- 语法:
class MyClass { #privateField; constructor() { this.#privateField = 'private value'; } getPrivateValue() { return this.#privateField; } }
。 - 例如:
class Counter { #count = 0; increment() { this.#count++; } get count() { return this.#count; } } const myCounter = new Counter(); myCounter.increment(); console.log(myCounter.count); // 1 // 以下代码会报错,因为无法从外部访问#count // console.log(myCounter.#count);
- 语法:
- 基本概念
async/await
的进一步应用和优化Promise.allSettled
与async/await
结合Promise.allSettled
返回一个Promise
,该Promise
在所有给定的Promise
都已经完成(无论成功或失败)后才会完成。可以和async/await
结合来处理多个异步操作。例如:async function handleMultiplePromises() { const promises = [ Promise.resolve('success1'), Promise.reject('error2'), Promise.resolve('success3') ]; const results = await Promise.allSettled(promises); for (const result of results) { if (result.status === 'fulfilled') { console.log('Fulfilled:', result.value); } else { console.log('Rejected:', result.reason); } } } handleMultiplePromises();
- 这个例子中,
Promise.allSettled
会等待所有Promise
完成,然后通过async/await
可以方便地处理每个Promise
的结果,无论是成功还是失败。
Promise.allSettled 场景应用
-
批量处理异步操作并获取所有结果
- 场景描述
- 当需要同时发起多个异步操作(如多个网络请求、多个文件读取等),并且希望获取每个异步操作的最终状态(无论成功还是失败)时,
Promise.allSettled
非常有用。
- 当需要同时发起多个异步操作(如多个网络请求、多个文件读取等),并且希望获取每个异步操作的最终状态(无论成功还是失败)时,
- 示例代码
- 假设需要同时获取多个用户的信息,这些用户信息可能来自不同的API端点。
const userIds = [1, 2, 3]; const userInfoPromises = userIds.map((id) => { return fetch(`https://api.example.com/users/${id}`) .then((response) => response.json()) .catch((error) => ({error})); }); Promise.allSettled(userInfoPromises).then((results) => { results.forEach((result) => { if (result.status === 'fulfilled') { console.log('用户信息获取成功:', result.value); } else { console.log('用户信息获取失败:', result.reason); } }); });
- 在这个例子中,通过
map
函数为每个用户ID创建一个获取用户信息的Promise
,这些Promise
被收集到userInfoPromises
数组中。然后使用Promise.allSettled
来等待所有Promise
完成,之后遍历结果数组,分别处理成功和失败的情况。
- 假设需要同时获取多个用户的信息,这些用户信息可能来自不同的API端点。
- 场景描述
-
事务处理或多步骤操作中的错误处理
- 场景描述
- 在涉及多个步骤的事务性操作中(例如,在数据库事务中同时插入多条记录、更新多个数据等操作),如果希望执行所有操作,然后统一处理所有可能出现的错误,
Promise.allSettled
可以提供帮助。
- 在涉及多个步骤的事务性操作中(例如,在数据库事务中同时插入多条记录、更新多个数据等操作),如果希望执行所有操作,然后统一处理所有可能出现的错误,
- 示例代码
- 假设要同时更新一个用户的多个信息(如姓名、年龄和地址),这些更新操作可能是独立的数据库更新操作。
const updateNamePromise = updateUserInfo('name', 'John Doe'); const updateAgePromise = updateUserInfo('age', 30); const updateAddressPromise = updateUserInfo('address', '123 Main St'); const updatePromises = [updateNamePromise, updateAgePromise, updateAddressPromise]; Promise.allSettled(updatePromises).then((results) => { let hasError = false; results.forEach((result) => { if (result.status === 'rejected') { console.log('更新用户信息出错:', result.reason); hasError = true; } }); if (!hasError) { console.log('用户信息更新成功'); } }); function updateUserInfo(field, value) { return new Promise((resolve, reject) => { // 假设这是一个模拟的数据库更新操作,可能会出错 const success = Math.random() > 0.2; if (success) { resolve({field, value}); } else { reject(new Error(`更新${field}出错`)); } }); }
- 这里创建了三个更新用户信息的
Promise
,将它们放入数组后使用Promise.allSettled
等待所有操作完成。然后检查每个操作的结果,如果有任何一个操作失败,就记录错误信息,最后根据是否有错误来判断整个更新过程是否成功。
- 假设要同时更新一个用户的多个信息(如姓名、年龄和地址),这些更新操作可能是独立的数据库更新操作。
- 场景描述
-
并行加载资源(如模块、图片等)
- 场景描述
- 在前端开发中,可能需要同时加载多个资源(如多个JavaScript模块、多张图片等),并且要对加载的结果进行统一处理,确保所有资源都尝试加载后再进行下一步操作。
- 示例代码
- 假设要同时加载多个图片并在页面上显示加载状态。
const imageUrls = ['image1.jpg', 'image2.jpg', 'image3.jpg']; const imagePromises = imageUrls.map((url) => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error(`图片${url}加载失败`)); img.src = url; }); }); Promise.allSettled(imagePromises).then((results) => { const loadedImages = []; results.forEach((result) => { if (result.status === 'fulfilled') { loadedImages.push(result.value); } else { console.log('图片加载失败:', result.reason); } }); // 将加载成功的图片添加到页面的某个元素中 const imageContainer = document.getElementById('image - container'); loadedImages.forEach((img) => { imageContainer.appendChild(img); }); });
- 首先为每个图片URL创建一个加载图片的
Promise
,使用Promise.allSettled
等待所有图片加载完成,然后将加载成功的图片添加到页面的指定容器中,同时记录加载失败的图片信息。
- 假设要同时加载多个图片并在页面上显示加载状态。
- 场景描述