15、JavaScript异步编程、迭代器与生成器全解析

JavaScript异步编程、迭代器与生成器全解析

1. 异步JavaScript

在JavaScript中,异步操作是常见的,如数据获取。以下是一个简单的异步数据获取示例:

fetchData(/* some url */)
  .then((data) => {
    /* do something with the data */
  })
  .catch((error) => {
    /* handle error */
  });

这里的 fetchData 函数用于获取数据, then 方法处理成功响应, catch 方法处理错误。也可以在 fetchData 函数内部处理错误,代码如下:

function fetchData(url) {
  fetch(url)
    .then((response) => response.json())
    .then((data) => {
      /* do something with the data */
    })
    .catch((err) => {
      /* error handling code */
    });
}

fetchData(/* some url */);
1.1 承诺拒绝转换为履行

Promise.prototype 对象上的每个方法都会返回一个新的承诺。如果不小心,可能会编写将承诺拒绝隐式转换为承诺履行的代码。例如:

function getData(url) {
  return Promise.reject(new Error()).catch((err) => {
    console.log("inside catch block in getData function");
  });
}

getData()
  .then((data) => console.log("then block"))
  .catch((error) => console.log("catch block"));

预期输出可能是“catch block”,但实际输出是:

"inside catch block in getData function"
"then block"

代码执行流程如下:
1. 调用 getData 函数。
2. Promise.reject(new Error()) 创建一个被拒绝的承诺。
3. 由于承诺被拒绝, catch 方法的回调函数被调用。
4. 控制台输出“inside catch block in getData function”。
5. 因为 catch 方法的回调函数没有显式返回任何内容,所以隐式返回 undefined
6. catch 方法返回的承诺以其回调函数的返回值(即 undefined )被履行。
7. getData 函数将这个已履行的承诺返回给调用代码。
8. 由于 getData 函数返回的承诺以值 undefined 被履行,调用代码中 then 方法的回调被调用,输出“then block”。

要修复这个问题,有两种方法:
- catch 方法的回调函数中抛出错误

function getData(url) {
  return Promise.reject(new Error()).catch((err) => {
    throw err;
  });
}

这样会拒绝 catch 方法返回的承诺,调用代码中的 catch 方法回调将被调用。
- 移除 catch 方法调用

function getData(url) {
  return Promise.reject(new Error());
}

这也会调用调用代码中的 catch 块,因为 getData 函数现在返回 Promise.reject 的结果,而 Promise.reject 会创建一个被拒绝的承诺。

1.2 异步执行器函数

使用 Promise 构造函数创建新承诺时,会传递一个函数给构造函数,这个函数称为执行器函数。执行器函数不应该是异步函数。例如:

const p = new Promise(async (resolve, reject) => {
  throw new Error("error");
});

p.catch((e) => console.log(e.message));

由于执行器函数是异步函数,其中抛出的错误不会导致新创建的承诺被拒绝, catch 方法的回调函数不会被调用。如果执行器函数是同步函数,其中抛出的任何错误都会自动拒绝新创建的承诺。

2. 迭代器和生成器
2.1 for...of 循环

内置对象(如数组)可以使用 for...of 循环进行迭代。与简单的 for 循环相比, for...of 循环更方便,不需要手动处理索引和循环终止条件。示例如下:

const arr = [1, 2, 3];

for (const num of arr) {
  console.log(num);
}

要理解 for...of 循环如何帮助我们迭代数组,需要了解两个概念:可迭代对象和迭代器。

2.2 可迭代对象

可迭代对象是实现了可迭代协议的对象。根据可迭代协议,如果一个对象定义了可被 for...of 循环用于迭代其值的迭代行为,则该对象是可迭代的。对象可以实现由 Symbol.iterator 表示的属性所引用的方法,这个方法返回一个迭代器对象。例如,数组的 Symbol.iterator 方法定义在 Array.prototype 对象中。

2.3 迭代器

迭代器是实现了迭代器协议的对象。根据迭代器协议,一个对象如果实现了名为 next 的方法,该方法接受零个或一个参数,并返回一个具有以下属性的对象,则该对象是迭代器:
- done :指示迭代器是否可以产生或返回另一个值。如果可以,其值为 false ,否则为 true
- value :迭代器对象返回的值。如果 done 属性的值为 true ,则该属性可以省略或其值可以为 undefined

迭代器对象示例:

{ value: 45, done: false }
{ value: undefined, done: true }

可以直接使用数组的迭代器来获取数组中的值,代码如下:

const arr = [2, 4, 6, 8, 10];
const arrayIterator = arr[Symbol.iterator]();
let result = arrayIterator.next();

while (!result.done) {
  console.log(result.value);
  result = arrayIterator.next();
}

内置的迭代器对象为可迭代对象定义了特定的迭代行为。例如, Map 对象的迭代器使用示例:

const myMap = new Map();
myMap.set("a", 1);
myMap.set("b", 2);
myMap.set("c", 3);

const mapIterator = myMap[Symbol.iterator]();
let result = mapIterator.next();

while (!result.done) {
  console.log(result.value);
  result = mapIterator.next();
}

可以使用 Map.prototype.values() 获取只返回 Map 中值的迭代器,或使用 Map.prototype.keys() 获取返回 Map 中所有键的迭代器。

2.4 迭代器原型

每个迭代器对象都继承自相应的迭代器原型对象,所有迭代器原型对象都继承自 Iterator.prototype 对象。例如:

const arr = [2, 4, 6, 8, 10];
const arrayIterator = arr[Symbol.iterator]();
const arrayIteratorPrototype = Object.getPrototypeOf(arrayIterator);
console.log(Object.getOwnPropertyNames(arrayIteratorPrototype)); 
// ["next"]

Iterator.prototype 对象本身是一个可迭代对象,这意味着任何迭代器对象本身都是可迭代的,可以与 for...of 循环一起使用。例如:

const arr = [1, 2, 3];
const arrayIterator = arr[Symbol.iterator]();

for (const num of arrayIterator) {
  console.log(num);
}
2.5 创建自定义可迭代对象

了解了可迭代对象和迭代器后,可以创建自定义可迭代对象。例如,创建可迭代的学生对象:

function Student(name, age, id, courses) {
  this.name = name;
  this.age = age;
  this.id = id;
  this.courses = courses;
}

Student.prototype[Symbol.iterator] = function () {
  const currentStudent = this;
  const studentProps = Object.getOwnPropertyNames(currentStudent);
  let propIndex = 0;

  const studentIterator = {
    next: () => {
      if (propIndex < studentProps.length) {
        const key = studentProps[propIndex];
        const value = currentStudent[key];
        propIndex++;
        const formattedValue = `${key.padStart(7)} => ${value}`;

        return {
          value: formattedValue,
          done: false
        };
      }

      return {
        value: undefined,
        done: true
      };
    }
  };

  return studentIterator;
};

const jack = new Student("Jack", 20, "21A", ["Maths", "Biology", "Physics"]);

for (const val of jack) {
  console.log(val);
}

默认情况下,学生迭代器对象不可迭代。可以通过在学生迭代器对象中实现 Symbol.iterator 方法使其可迭代:

Student.prototype[Symbol.iterator] = function () {
  const studentIterator = {
    next() {
      // code omitted to keep code example short
    },
    [Symbol.iterator]() {
      return this;
    }
  };

  return studentIterator;
};

还可以使用 Object.defineProperty 方法将 Symbol.iterator 方法定义为不可枚举:

Object.defineProperty(Student.prototype, Symbol.iterator, {
  value: function () {
    // copy the code inside the Symbol.iterator method from above
  },
  configurable: true,
  writable: true
});
2.6 生成器

生成器是JavaScript中的特殊函数,可以在执行过程中的不同点暂停执行,然后从暂停点继续执行。生成器函数可以用于生成一系列值,可被 for...of 循环等结构消费。例如,生成0到10之间的奇数:

function* odds() {
  for (let i = 1; i <= 10; i += 2) {
    yield i;
  }
}

for (const num of odds()) {
  console.log(num);
}

生成器函数的特点:
- function* 语法将函数标记为生成器函数。
- yield 关键字产生一个值,调用生成器函数返回的迭代器对象的 next 方法时,会得到 yield 关键字右侧的值, yield 关键字也标记了生成器函数暂停的位置。

生成器函数还可以创建无限序列,例如生成随机数:

function* randomNumberGenerator(max) {
  while (true) {
    yield Math.floor(Math.random() * max);
  }
}

const randomNumGen = randomNumberGenerator(10);

for (let i = 0; i < 10; i++) {
  console.log(randomNumGen.next().value);
}

生成器函数是惰性求值的,其执行会暂停,直到请求新的值。

2.7 使用生成器实现迭代器

由于生成器函数返回一个迭代器对象,因此可以方便地编写迭代器。可以使用生成器函数重写学生迭代器示例:

function Student(name, age, id, courses) {
  this.name = name;
  this.age = age;
  this.id = id;
  this.courses = courses;
}

Student.prototype[Symbol.iterator] = function* () {
  const currentStudent = this;
  const studentProps = Object.getOwnPropertyNames(currentStudent);

  for (let i = 0; i < studentProps.length; i++) {
    const key = studentProps[i];
    const value = currentStudent[key];
    const formattedValue = `${key.padStart(7)} => ${value}`;

    yield formattedValue;
  }
};

const jack = new Student("Jack", 20, "21A", ["Maths", "Biology", "Physics"]);

for (const val of jack) {
  console.log(val);
}

使用生成器函数实现的迭代器代码更简单、简洁、易读,并且生成器函数返回的迭代器对象自动可迭代。

总结

本文详细介绍了JavaScript中的异步编程、迭代器和生成器的相关知识。异步编程中要注意承诺拒绝和履行的转换问题,避免代码出现意外行为。迭代器和生成器为处理序列数据提供了强大的工具,可以方便地创建自定义可迭代对象和生成序列值。通过合理运用这些特性,可以编写出更高效、更具可读性的JavaScript代码。

3. 技术点对比与应用场景分析

3.1 异步编程技术对比
技术 优点 缺点 应用场景
普通 Promise 链式调用 代码结构清晰,易于理解和维护 可能出现回调地狱问题 简单的异步操作链,如数据获取和处理
解决承诺拒绝转换问题的 Promise 避免承诺拒绝隐式转换为履行,确保错误能被正确处理 可能增加代码复杂度 涉及网络请求等可能失败的异步操作
异步执行器函数(避免使用异步) 确保错误能正确导致承诺拒绝 需注意执行器函数不能为异步 自定义承诺创建场景

例如,在一个简单的用户信息获取应用中,使用普通 Promise 链式调用:

function fetchUserInfo() {
  return fetch('https://example.com/user')
    .then(response => response.json())
    .then(data => {
      console.log('User info:', data);
      return data;
    })
    .catch(error => {
      console.error('Error fetching user info:', error);
    });
}

fetchUserInfo();
3.2 迭代器与生成器应用场景对比
技术 优点 缺点 应用场景
迭代器 可自定义迭代行为,直接控制迭代过程 代码实现相对复杂 自定义对象迭代,如自定义数据结构
生成器 代码简洁,自动实现迭代器协议,支持无限序列 理解难度稍大 生成序列数据,如生成随机数、斐波那契数列等

例如,在一个学生信息管理系统中,使用迭代器自定义学生对象迭代:

function Student(name, age, id, courses) {
  this.name = name;
  this.age = age;
  this.id = id;
  this.courses = courses;
}

Student.prototype[Symbol.iterator] = function () {
  const currentStudent = this;
  const studentProps = Object.getOwnPropertyNames(currentStudent);
  let propIndex = 0;

  const studentIterator = {
    next: () => {
      if (propIndex < studentProps.length) {
        const key = studentProps[propIndex];
        const value = currentStudent[key];
        propIndex++;
        const formattedValue = `${key.padStart(7)} => ${value}`;

        return {
          value: formattedValue,
          done: false
        };
      }

      return {
        value: undefined,
        done: true
      };
    }
  };

  return studentIterator;
};

const jack = new Student("Jack", 20, "21A", ["Maths", "Biology", "Physics"]);

for (const val of jack) {
  console.log(val);
}

而在一个游戏开发中,使用生成器生成随机关卡编号:

function* randomLevelGenerator(max) {
  while (true) {
    yield Math.floor(Math.random() * max);
  }
}

const levelGen = randomLevelGenerator(10);

for (let i = 0; i < 5; i++) {
  console.log('Random level:', levelGen.next().value);
}

4. 操作步骤总结

4.1 异步编程错误处理操作步骤
  1. 识别问题 :检查代码中是否存在承诺拒绝隐式转换为履行的情况,例如在 catch 方法中未正确处理错误。
  2. 选择解决方案
    • 如果需要在 catch 方法中处理错误并继续传递错误,使用 throw err
    • 如果不需要在当前函数中处理错误,直接移除 catch 方法调用。
  3. 修改代码 :根据选择的解决方案修改代码,确保错误能被正确处理。
4.2 创建自定义可迭代对象操作步骤
  1. 定义对象构造函数 :如定义 Student 构造函数,用于创建对象实例。
  2. 实现 Symbol.iterator 方法 :在对象的原型上实现 Symbol.iterator 方法,该方法返回一个迭代器对象。
  3. 定义迭代器对象的 next 方法 :在迭代器对象中定义 next 方法,根据迭代逻辑返回具有 value done 属性的对象。
  4. 可选:使迭代器对象可迭代 :在迭代器对象中实现 Symbol.iterator 方法,返回 this
  5. 可选:使 Symbol.iterator 方法不可枚举 :使用 Object.defineProperty 方法定义 Symbol.iterator 方法。
4.3 使用生成器实现迭代器操作步骤
  1. 定义对象构造函数 :如定义 Student 构造函数。
  2. 使用生成器函数实现 Symbol.iterator 方法 :在对象的原型上使用 function* 定义 Symbol.iterator 方法,使用 yield 关键字生成迭代值。
  3. 使用对象进行迭代 :创建对象实例,使用 for...of 循环进行迭代。

5. 流程图展示

graph TD;
    A[开始] --> B[异步编程];
    B --> B1[普通Promise链式调用];
    B --> B2[解决承诺拒绝转换问题];
    B --> B3[异步执行器函数];
    C[迭代器和生成器];
    C --> C1[迭代器];
    C --> C2[生成器];
    B1 --> D[简单异步操作链];
    B2 --> E[网络请求等可能失败的操作];
    B3 --> F[自定义承诺创建];
    C1 --> G[自定义对象迭代];
    C2 --> H[生成序列数据];
    D --> I[结束];
    E --> I;
    F --> I;
    G --> I;
    H --> I;

6. 总结与展望

通过本文的介绍,我们深入了解了JavaScript中的异步编程、迭代器和生成器技术。异步编程让我们能够更好地处理异步操作,避免阻塞主线程;迭代器和生成器为处理序列数据提供了强大的工具,使代码更加灵活和高效。

在未来的JavaScript开发中,这些技术将继续发挥重要作用。随着Web应用的不断发展,异步操作会越来越常见,正确处理异步错误和优化异步代码将成为开发者的重要技能。同时,迭代器和生成器也将在数据处理、算法实现等领域得到更广泛的应用。开发者可以进一步探索这些技术的高级应用,如使用 async / await 结合生成器实现更复杂的异步迭代等。

希望本文能帮助读者更好地掌握这些技术,在实际开发中编写出更优秀的JavaScript代码。

基于径向基函数神经网络RBFNN的自适应滑模控制学习(Matlab代码实现)内容概要:本文介绍了基于径向基函数神经网络(RBFNN)的自适应滑模控制方法,并提供了相应的Matlab代码实现。该方法结合了RBF神经网络的非线性逼近能力和滑模控制的强鲁棒性,用于解决复杂系统的控制问题,尤其适用于存在不确定性和外部干扰的动态系统。文中详细阐述了控制算法的设计思路、RBFNN的结构权重更新机制、滑模面的构建以及自适应律的推导过程,并通过Matlab仿真验证了所提方法的有效性和稳定性。此外,文档还列举了大量相关的科研方向和技术应用,涵盖智能优化算法、机器学习、电力系统、路径规划等多个领域,展示了该技术的广泛应用前景。; 适合人群:具备一定自动控制理论基础和Matlab编程能力的研究生、科研人员及工程技术人员,特别是从事智能控制、非线性系统控制及相关领域的研究人员; 使用场景及目标:①学习和掌握RBF神经网络滑模控制相结合的自适应控制策略设计方法;②应用于电机控制、机器人轨迹跟踪、电力电子系统等存在模型不确定性或外界扰动的实际控制系统中,提升控制精度鲁棒性; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,深入理解算法实现细节,同时可参考文中提及的相关技术方向拓展研究思路,注重理论分析仿真验证相结合。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值