6、JavaScript 异步编程详解

13 阅读4分钟

1、Promises

Promises 提供了一种干净的方式来处理异步操作及其结果。以下是 Promise 的详细方法及更多示例:

Promise.resolve()

Promise.resolve() 方法返回一个以给定值解析的 Promise。

const resolvedPromise = Promise.resolve(42);
resolvedPromise.then(value => console.log(value)); // 42

Promise.reject()

Promise.reject() 方法返回一个带有给定原因的被拒绝的 Promise。

const rejectedPromise = Promise.reject("Error occurred");
rejectedPromise.catch(reason => console.error(reason)); // Error occurred

Promise.allSettled()

Promise.allSettled() 方法返回一个 Promise,当所有给定的 Promise 都已敲定(resolved 或 rejected)后,它会异步地解析这个 Promise,结果是一个对象数组。

const p1 = Promise.resolve(1);
const p2 = Promise.reject("Failed");
const p3 = Promise.resolve(3);

Promise.allSettled([p1, p2, p3]).then(results => {
    results.forEach(result => console.log(result.status, result.value));
    // fulfilled 1
    // rejected Failed
    // fulfilled 3
});

Promise.any()

Promise.any() 方法返回第一个成功决议的 Promise,如果全是被拒绝的 Promises,则返回一个 AggregateError。

const p1 = Promise.reject("Failed");
const p2 = Promise.resolve(2);

Promise.any([p1, p2]).then(value => {
    console.log(value); // 2
}).catch(error => {
    console.error(error);
});

2、Async/Await

async/await 是基于 Promises 构建的,用于简化异步代码的语法,并使其更加可读。

使用多个 await

你可以连续使用多个 await 来处理依次的异步操作。

async function asyncFunction() {
    try {
        const result1 = await asyncOperation1();
        console.log(result1);
        const result2 = await asyncOperation2();
        console.log(result2);
    } catch (error) {
        console.error(error);
    }
}

asyncFunction();

3、Generators

Generators 允许函数执行中途退出并在稍后恢复,保存其上下文(变量绑定)。

使用 Generators 和 Promises

将 Generators 和 Promises 结合使用来处理异步代码。

function asyncOperation() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Operation done");
        }, 2000);
    });
}

function* generatorFunction() {
    const result = yield asyncOperation();
    console.log(result);
}

const iterator = generatorFunction();
iterator.next().value.then(result => iterator.next(result));

4、使用 co 库

co 库可以帮助我们将 Generator 用于异步编程,非常类似于 async/await

const co = require('co');

function asyncOperation() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Operation done");
        }, 2000);
    });
}

co(function* () {
    const result = yield asyncOperation();
    console.log(result); // Operation done
}).catch(error => {
    console.error(error);
});

5、Observables

Observables 是一种比 Promise 更强大的异步机制,主要通过 ReactiveX 库(如 RxJS)实现。

创建和订阅 Observable

以下是如何使用 RxJS 创建和订阅一个 Observable:

const { Observable } = require('rxjs');

// 创建一个 Observable
const observable = new Observable(subscriber => {
    subscriber.next('Hello');
    setTimeout(() => {
        subscriber.next('World');
        subscriber.complete();
    }, 1000);
});

// 订阅一个 Observable
observable.subscribe({
    next(x) { console.log(x); },
    error(err) { console.error('Error: ' + err); },
    complete() { console.log('Done'); }
});

RxJS 操作符

RxJS 提供了丰富的操作符来处理数据流(如映射、过滤、合并等)。

const { of } = require('rxjs');
const { map, filter } = require('rxjs/operators');

// 创建一个 Observable,并使用操作符处理数据流
const observable = of(1, 2, 3, 4, 5).pipe(
    filter(x => x % 2 === 0),
    map(x => x * 10)
);

observable.subscribe({
    next(x) { console.log(x); }, // 20, 40
    complete() { console.log('Done'); }
});

7、Async Iterators

Async Iterators 允许你定义一个可以暂停和恢复的函数,用于处理异步数据流。

创建一个 Async Iterator 并使用 for-await-of 循环

async function* asyncGenerator() {
    const data = ['Hello', 'World', 'Async', 'Iteration'];
    for (let item of data) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        yield item;
    }
}

// 使用 for-await-of 循环遍历 AsyncIterator
async function processAsyncIterable() {
    for await (let value of asyncGenerator()) {
        console.log(value);
    }
}

processAsyncIterable();

8、Web Workers

Web Workers 允许你在一个后台线程运行 JavaScript,避免阻塞主线程的执行。

创建和使用 Web Worker

main.js

const worker = new Worker('worker.js');

worker.onmessage = function(event) {
    console.log('Received from worker:', event.data);
};

worker.postMessage('Hello from main thread');

worker.js

onmessage = function(event) {
    console.log('Received from main thread:', event.data);
    postMessage('Hello from worker thread');
};

复杂计算任务示例

main.js

const worker = new Worker('prime-worker.js');

worker.onmessage = function(event) {
    console.log('Prime numbers:', event.data);
};

worker.postMessage(100000); // Find prime numbers up to 100000

prime-worker.js

function findPrimes(limit) {
    const primes = [];
    for (let i = 2; i <= limit; i++) {
        let isPrime = true;
        for (let j = 2; j <= Math.sqrt(i); j++) {
            if (i % j === 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) primes.push(i);
    }
    return primes;
}

onmessage = function(event) {
    const limit = event.data;
    const primes = findPrimes(limit);
    postMessage(primes);
};

9、Service Workers

Service Workers 是运行在浏览器后台的独立脚本,适用于实现离线缓存、推送通知、后台数据同步等功能。

注册和使用 Service Worker

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Service Worker Example</title>
</head>
<body>
    <script>
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('service-worker.js')
                .then(registration => {
                    console.log('Service Worker registered:', registration);
                }).catch(error => {
                    console.error('Service Worker registration failed:', error);
                });
        }
    </script>
</body>
</html>

service-worker.js

self.addEventListener('install', event => {
    console.log('Service Worker installing.');
});

self.addEventListener('activate', event => {
    console.log('Service Worker activating.');
});

self.addEventListener('fetch', event => {
    console.log('Fetching:', event.request.url);
    event.respondWith(fetch(event.request)); // 可在此处实现离线缓存逻辑
});

10、setTimeout 和 setInterval

setTimeout

setTimeout 用于在指定的时间间隔后执行一段代码。

setTimeout(() => {
    console.log('Executed after 2 seconds');
}, 2000);

setInterval

setInterval 用于每隔指定的时间间隔执行一段代码。

const intervalId = setInterval(() => {
    console.log('Executed every 1 second');
}, 1000);

// 停止执行
setTimeout(() => {
    clearInterval(intervalId);
    console.log('Interval cleared');
}, 5000);

11、Event Loop 和 Microtasks/Macrotasks

了解 JavaScript 的 Event Loop 机制对于理解异步编程非常重要。

事件循环示例

console.log('Script start');

setTimeout(() => {
    console.log('setTimeout'); // Macrotask
}, 0);

new Promise((resolve) => {
    console.log('Promise start');
    resolve();
}).then(() => {
    console.log('Promise then'); // Microtask
});

console.log('Script end');

输出顺序:

Script start
Promise start
Script end
Promise then
setTimeout

Microtasks (e.g., Promises)

Microtasks 在每一个循环迭代结束后执行,优先级高于 Macrotasks。

Promise.resolve().then(() => {
    console.log('Microtask 1');
}).then(() => {
    console.log('Microtask 2');
});

Macrotasks (e.g., setTimeout, setInterval)

Macrotasks 在每一个 Event Loop 周期结束时执行,优先级低于 Microtasks。

setTimeout(() => {
    console.log('Macrotask 1');
}, 0);

setTimeout(() => {
    console.log('Macrotask 2');
}, 0);

这些是 JavaScript 处理异步编程的其他一些方案,每种方法都有各自的适用场景和优缺点。通过适当地选择工具和方法,你可以更高效地处理异步任务,使代码更加简洁、可维护和高效。