发布时间:JavaScript诞生之初就已经有了回调函数这种异步编程模型。
最初,JavaScript使用回调函数处理异步操作,如处理网络请求、定时器等。
当异步操作完成后,预先定义好的回调函数会被调用,通常作为参数传递给执行异步操作的函数。
function getData(callback) {
setTimeout(() => {
let data = 'Some data';
callback(data);
}, 1000);
}
getData((data) => {
console.log(data); // 'Some data',在1秒后异步打印
});
发布时间:ES6(ECMAScript 2015)
Promise是ES6引入的标准化解决方案,用于处理异步操作。Promise对象代表一个异步操作的最终完成(resolve)或失败(reject),它提供了链式调用和错误处理机制,解决了回调地狱的问题。
function getData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let data = 'Some data';
resolve(data);
}, 1000);
});
}
getData().then(data => {
console.log(data); // 'Some data',在1秒后异步打印
});
发布时间:ES6(ECMAScript 2015)
ES6中的Generator是一种迭代器协议的实现,它允许函数暂停执行和恢复执行,从而支持异步流程控制。配合yield关键字和第三方库(如co模块)可以实现异步操作的同步化书写风格。
生成器是一种特殊的迭代器,但不同于普通迭代器由类或工厂函数创建,生成器是由生成器函数生成的。
生成器函数通过在函数声明前放置星号(*)来定义,例如function* generatorFunc() {…}。
在生成器函数内部,可以使用yield关键字暂停函数的执行并产出一个值。每次调用生成器对象的next()方法时,函数会在上次暂停的地方继续执行直到遇到下一个yield表达式。
生成器的强大之处在于它可以控制迭代过程,不仅可以产出值,还可以接收外部传入的值(通过调用next(value)时传入的参数),从而实现双向通信。
function* asyncData() {
let data = yield fetchSomeDataAsync();
console.log(data);
}
function fetchSomeDataAsync() {
return new Promise(resolve => {
setTimeout(() => resolve('Some data'), 1000);
});
}
// 需要配合第三方库或async/await实现异步执行
co(asyncData()); // 使用co模块执行生成器
注意:在实际项目中,由于现代JavaScript环境普遍支持async/await,因此直接使用async/await编写异步代码更为常见和简洁。而在过去,当async/await还未被广泛支持时,co库曾被广泛应用于处理生成器函数的异步流程控制。而现在,除非特殊场景或兼容旧代码,一般很少会再特意使用co库。
尽管 async/await 成为主流,但这并不意味着生成器失去了价值。在一些特定场景下,比如复杂的迭代器逻辑或者需要细粒度控制异步流程时,生成器依然有用武之地。
生成器还可以用于协程(coroutines)、流处理等场景,特别是在一些库和框架中,如 Koa.js(早期版本)中作为中间件的核心技术,即使现在许多新项目也倾向于迁移到使用 async/await
// 创建一个简单数组
const array = [1, 2, 3, 4, 5];
// 自定义迭代器类
class ArrayIterator {
constructor(arr) {
this.index = 0;
this.array = arr;
}
// 实现Symbol.iterator方法
[Symbol.iterator]() {
return this;
}
// 定义next方法
next() {
if (this.index < this.array.length) {
return { value: this.array[this.index++], done: false };
} else {
return { done: true }; // 没有更多元素了
}
}
}
// 使用方法一:使用for...of循环遍历
for (const value of iteratorArray) {
console.log(value);
}
//使用方法一:手动迭代
let iteration = iterator.next();
while (!iteration.done) {
console.log(iteration.value);
iteration = iterator.next();
}
在JavaScript中,许多内置对象已经内置了迭代器,可以直接用于for…of
循环或其他迭代操作。
比如:数组 (Array)、字符串 (String)、Set 和 Map、TypedArray (如 Uint8Array, Int16Array 等)、NodeList(DOM API中的节点列表)
const array = [1, 2, 3];
const str = "hello";
const map = new Map([[1, 'one'], [2, 'two']]);
const set = new Set([1, 2, 3]);
const typedArray = new Uint8Array([1, 2, 3]);
const divs = document.querySelectorAll('div');
//下面【divs】替换成以上其中一个变量,都可遍历
for (const div of divs) {
console.log(div);
}
发布时间:ES8(ECMAScript 2017)
ES7引入了async函数和await关键字,它们是基于Promise的更高级抽象,使异步代码看起来更接近同步代码,大大提升了代码可读性和易用性。
async 表示这是一个async函数, await只能用在async函数里面,不能单独使用
async 返回的是一个Promise对象,await就是等待这个promise的返回结果后,再继续执行
await 等待的是一个Promise对象,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值
async function getData() {
let data = await fetchSomeDataAsync();
console.log(data);
}
async function fetchSomeDataAsync() {
return new Promise(resolve => {
setTimeout(() => resolve('Some data'), 1000);
});
}
getData(); // 使用await关键字等待异步操作完成
发布时间:ES2018(ECMAScript 2018)
异步迭代器和异步生成器是迭代器和生成器的异步版本,用于处理异步生成的值序列,如从数据库查询记录等。
异步迭代器和异步生成器的区别在于它们处理异步值的能力。
异步迭代器通过实现 Symbol.asyncIterator 协议来返回一个异步迭代器对象。
而异步生成器则是可以包含 await 表达式的特殊生成器函数,它能够暂停并在异步操作完成时恢复执行。
// 异步生成器函数
async function* asyncGenerator() {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 1000));
yield 'First value (after 1s)';
await new Promise(resolve => setTimeout(resolve, 500));
yield 'Second value (after another 0.5s)';
}
// 使用异步迭代器
(async () => {
const ag = asyncGenerator();
for await (const value of ag) {
console.log(value);
}
})();
总结起来,从ES6开始,JavaScript的异步编程模型得到了极大的丰富和完善,目前async/await是主流的异步编程方式