Skip to content

异步编程

题目1: 什么是回调函数?如何避免回调地狱?

答案: 回调函数是作为参数传递给另一个函数的函数,它在某个操作完成后被调用。回调地狱是指多层嵌套的回调函数,使得代码难以阅读和维护。

避免回调地狱的方法:

  1. 使用 Promise
  2. 使用 async/await
  3. 模块化和函数命名
  4. 使用控制流程库(如 async.js)

扩展:

javascript
// 回调地狱示例
getData(function(a) {
    getMoreData(a, function(b) {
        getMoreData(b, function(c) {
            getMoreData(c, function(d) {
                getMoreData(d, function(e) {
                    // ...
                });
            });
        });
    });
});

// 使用 Promise 改写
getData()
    .then(a => getMoreData(a))
    .then(b => getMoreData(b))
    .then(c => getMoreData(c))
    .then(d => getMoreData(d))
    .then(e => {
        // ...
    })
    .catch(error => {
        // 错误处理
    });

题目2: Promise 的基本用法和状态有哪些?

答案: Promise 是异步编程的一种解决方案,比传统的回调函数更加优雅。

Promise 的基本用法:

javascript
const promise = new Promise((resolve, reject) => {
    // 异步操作
    if (/* 操作成功 */) {
        resolve(value);
    } else {
        reject(error);
    }
});

promise.then(value => {
    // 成功的回调
}).catch(error => {
    // 失败的回调
});

Promise 有三种状态:

  1. Pending(进行中)
  2. Fulfilled(已成功)
  3. Rejected(已失败)

扩展: Promise 的状态一旦改变,就不会再变。这也就是 Promise 名字的由来,它的英语意思是"承诺",表示其状态的不可逆性。

题目3: async/await 的工作原理是什么?

答案: async/await 是建立在 Promise 之上的语法糖,让异步代码看起来像同步代码。

  • async 函数返回一个 Promise 对象
  • await 关键字只能在 async 函数内部使用
  • await 会暂停函数的执行,等待 Promise 解决

扩展:

javascript
async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error:', error);
    }
}

题目4: 如何处理异步错误?

答案: 处理异步错误的主要方法有:

  1. 使用 Promise 的 .catch() 方法
  2. 在 async/await 中使用 try/catch 块
  3. 使用 Promise.all() 的错误处理
  4. 全局的未捕获 Promise 错误处理

扩展:

javascript
// Promise 错误处理
promise
    .then(result => {
        // 处理结果
    })
    .catch(error => {
        console.error('Error:', error);
    });

// async/await 错误处理
async function fetchData() {
    try {
        const data = await getData();
        // 处理数据
    } catch (error) {
        console.error('Error:', error);
    }
}

// 全局未捕获 Promise 错误处理
window.addEventListener('unhandledrejection', event => {
    console.error('Unhandled rejection:', event.reason);
});

题目5: 什么是 Promise.all() 和 Promise.race()?

答案:

  • Promise.all() 接收一个 Promise 数组作为输入,当所有 Promise 都成功时返回一个包含所有结果的数组,如果任何一个 Promise 失败,则立即返回失败的结果。
  • Promise.race() 同样接收一个 Promise 数组,但只要有一个 Promise 完成(无论成功或失败),就立即返回那个 Promise 的结果。

扩展:

javascript
// Promise.all() 示例
const promises = [fetch('/api/data1'), fetch('/api/data2'), fetch('/api/data3')];
Promise.all(promises)
    .then(results => {
        // 处理所有结果
    })
    .catch(error => {
        // 处理任何错误
    });

// Promise.race() 示例
const promise1 = new Promise(resolve => setTimeout(() => resolve('one'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('two'), 2000));
Promise.race([promise1, promise2])
    .then(result => console.log(result)); // 输出: "one"

题目6: 如何实现一个简单的 Promise?

答案: 实现一个简单的 Promise 需要考虑状态管理、异步操作、then 方法链式调用等。以下是一个基本实现:

javascript
class MyPromise {
    constructor(executor) {
        this.state = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];

        const resolve = value => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                this.onFulfilledCallbacks.forEach(cb => cb(value));
            }
        };

        const reject = reason => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                this.onRejectedCallbacks.forEach(cb => cb(reason));
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

        return new MyPromise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value);
                        resolve(x);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            }

            if (this.state === 'rejected') {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        resolve(x);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            }

            if (this.state === 'pending') {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onFulfilled(this.value);
                            resolve(x);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            const x = onRejected(this.reason);
                            resolve(x);
                        } catch (error) {
                            reject(error);
                        }
                    }, 0);
                });
            }
        });
    }
}

扩展: 这个实现包含了 Promise 的基本功能,但还缺少 catch、finally 等方法,以及更复杂的错误处理和 Promise 解析过程。完整的 Promise 实现需要遵循 Promises/A+ 规范。

题目7: 什么是 Generator 函数?它与异步编程有什么关系?

答案: Generator 函数是 ES6 引入的一种函数类型,可以被暂停和恢复执行。它们通过 yield 关键字来定义暂停点。

Generator 函数与异步编程的关系:

  1. 可以用于控制异步操作的流程
  2. 可以将异步操作变成同步的写法
  3. 是 async/await 的前身和基础

扩展:

javascript
function* asyncFlow() {
    const result1 = yield fetchData1();
    console.log(result1);
    const result2 = yield fetchData2(result1);
    console.log(result2);
}

function run(generator) {
    const iterator = generator();
    function handle(yielded) {
        if (!yielded.done) {
            yielded.value.then(result => handle(iterator.next(result)));
        }
    }
    handle(iterator.next());
}

run(asyncFlow);

题目8: 什么是事件循环(Event Loop)?

答案: 事件循环是 JavaScript 的执行模型,用于管理异步操作。它是一个持续运行的循环,不断地从任务队列中取出任务并执行。

事件循环的主要组成部分:

  1. 任务队列(Task Queue):存储待执行的任务
  2. 事件循环(Event Loop):不断地从任务队列中取出任务并执行
  3. 微任务队列(Microtask Queue):存储微任务,优先级高于宏任务

题目9: 什么是宏任务(Macro-task)和微任务(Micro-task)?

答案: 宏任务(Macro-task)和微任务(Micro-task)是事件循环中的两种任务类型。

宏任务:

  1. setTimeout
  2. setInterval
  3. setImmediate
  4. requestAnimationFrame

微任务:

  1. Promise
  2. MutationObserver
  3. process.nextTick(Node.js)

扩展: 在事件循环的每个周期中,宏任务和微任务的执行顺序是有规定的:

  1. 执行所有微任务
  2. 执行一个宏任务
  3. 重复上述过程,直到所有任务执行完毕

扩展:

javascript
// 示例代码
console.log('start');

setTimeout(() => {
    console.log('setTimeout');
});

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

console.log('end');

// 输出顺序:start -> end -> Promise -> setTimeout