面试题:JS中的Promise(3)

159 阅读6分钟

1627664298-javascript.webp

在JavaScript中,Promise 是一个表示异步操作最终完成(或失败)及其结果的对象。Promise 提供了一种更优雅的方式来处理异步操作,避免了回调地狱(callback hell)的问题。

Promise 的基本概念

状态

Promise 有三种状态:

  • Pending(进行中) :初始状态,既不是成功,也不是失败。
  • Fulfilled(已成功) :操作成功完成。
  • Rejected(已失败) :操作失败。

方法

  • then():用于处理异步操作成功和失败的回调函数。
  • catch():用于捕获异步操作中的错误。
  • finally():无论异步操作成功或失败,都会执行的回调函数。

创建 Promise

示例

const myPromise = new Promise((resolve, reject) => {
    // 模拟异步操作
    setTimeout(() => {
        const success = true; // 假设这是异步操作的结果
        if (success) {
            resolve('操作成功');
        } else {
            reject('操作失败');
        }
    }, 1000);
});

myPromise.then((result) => {
    console.log(result); // 输出:操作成功
}).catch((error) => {
    console.error(error); // 输出:操作失败
}).finally(() => {
    console.log('操作完成');
});

Promise 的链式调用

示例

function fetchData(url) {
    return new Promise((resolve, reject) => {
        // 模拟HTTP请求
        setTimeout(() => {
            const data = { id: 1, name: 'Kimi' };
            resolve(data);
        }, 1000);
    });
}

function process_data(data) {
    return new Promise((resolve, reject) => {
        // 模拟数据处理
        setTimeout(() => {
            const processedData = { ...data, processed: true };
            resolve(processedData);
        }, 1000);
    });
}

fetchData('https://api.example.com/data')
    .then(data => {
        console.log('获取数据成功:', data);
        return process_data(data);
    })
    .then(processedData => {
        console.log('处理数据成功:', processedData);
    })
    .catch(error => {
        console.error('发生错误:', error);
    });

方法

Promise.all

Promise.all 用于同时处理多个 Promise 对象。当所有 Promise 都成功时,Promise.all 返回一个新的 Promise。如果一个 Promise 失败,Promise.all 会立即返回一个失败的 Promise

示例

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
    console.log(values); // 输出:[3, 42, 'foo']
});

Promise.allSettled()

Promise.allSettled() 是 JavaScript 中的一个 Promise 方法,它接收一个 Promise 数组,并在所有 Promise 对象都已解决或拒绝后返回一个 Promise 对象,该对象解析为一个数组,每个元素对应相应的 Promise 对象的结果。

与 Promise.all() 不同的是,Promise.allSettled() 不会在任何 Promise 被拒绝时中止,并且总是等待所有 Promise 对象都已解决或拒绝后才返回结果。返回的数组中的每个元素都是一个对象,包含以下属性之一:

  • status:表示 Promise 的状态,可能的值为 “fulfilled”(已解决)或 “rejected”(已拒绝)。
  • value:如果 Promise 已解决,则为解决值;如果 Promise 已拒绝,则为拒绝原因。

举例:

Promise.allSettled([
  Promise.resolve(33),
  new Promise((resolve) => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error("一个错误")),
]).then((values) => console.log(values));

// [
//   { status: 'fulfilled', value: 33 },
//   { status: 'fulfilled', value: 66 },
//   { status: 'fulfilled', value: 99 },
//   { status: 'rejected', reason: Error: 一个错误 }
// ]

Promise.any()

执行所有 promise,返回最先返回的成功结果;全部失败才判定为失败

const pErr = new Promise((resolve, reject) => {
  reject("总是失败");
});

const pSlow = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, "最终完成");
});

const pFast = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "很快完成");
});

Promise.any([pErr, pSlow, pFast]).then((value) => {
  console.log(value);
  // pFast 第一个兑现
});

// 输出:很快完成

Promise.race

Promise.race(iterable) 方法返回一个 promise一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

示例

const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'one');
});
const promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 200, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
    console.log(value); // 输出:'one'
});

Promise.try()

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now

上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。

那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的。

const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next

鉴于这是一个很常见的需求,所以现在有一个提案,提供Promise.try方法替代上面的写法。

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

Promise.withResolvers()

在此之前我们创建 Promise 实例是这样的:

const promiseInstance = new Promise<void>((resolve,reject)=>{
  // ...
})

现在,我们也能够通过 Promise.withResolvers 来创建实例:

const { promise, resolve, reject } = Promise.withResolvers<void>()

但它并不是为了替代原有创建 Promise 实例的方式,引入它实际上是为了 打破 构造器模式创建 Promise 实例时,resolvereject 方法只能在构造器回调函数中使用的 限制

在它出现之前,我们也可以通过如下代码实现相同的效果:

let resolve, reject
const promise = new Promise((res, rej) => {
  resolve = res
  reject = rej
})

Promise 的错误处理

示例

function fetchData(url) {
    return new Promise((resolve, reject) => {
        // 模拟HTTP请求
        setTimeout(() => {
            const success = false; // 假设请求失败
            if (success) {
                resolve('数据获取成功');
            } else {
                reject('数据获取失败');
            }
        }, 1000);
    });
}

fetchData('https://api.example.com/data')
    .then(data => {
        console.log('获取数据成功:', data);
    })
    .catch(error => {
        console.error('发生错误:', error);
    });

async/await

async/await 是基于 Promise 的语法糖,使异步代码看起来更像同步代码,提高了代码的可读性和易用性。

示例

async function fetchData(url) {
    try {
        const response = await fetch(url);
        const data = await response.json();
        console.log('获取数据成功:', data);
    } catch (error) {
        console.error('发生错误:', error);
    }
}

fetchData('https://api.example.com/data');

JS中的其它异步处理机制

  • 回调函数(Callbacks)

回调函数是最基本的异步处理机制。通过将一个函数作为参数传递给另一个函数,并在异步操作完成后调用该回调函数,可以实现异步操作。

function fetchData(callback) {
    setTimeout(() => {
        const data = { id: 1, name: 'Kimi' };
        callback(data);
    }, 1000);
}

fetchData((data) => {
    console.log('数据获取成功:', data);
});
  • 事件监听(Event Listeners)

事件监听机制允许你为特定的事件注册回调函数。当事件发生时,注册的回调函数会被自动调用。这在DOM操作中非常常见。

document.getElementById('myButton').addEventListener('click', (event) => {
    console.log('按钮被点击');
});
  • 发布/订阅模式(Publish/Subscribe)

发布/订阅模式允许对象订阅特定的事件,并在事件发生时接收通知。这种模式在大型应用程序中非常有用,可以实现模块之间的解耦。

class EventEmitter {
    constructor() {
        this.events = {};
    }

    on(event, listener) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
    }

    emit(event, ...args) {
        if (this.events[event]) {
            this.events[event].forEach(listener => {
                listener(...args);
            });
        }
    }
}

const emitter = new EventEmitter();

emitter.on('data', (data) => {
    console.log('接收到数据:', data);
});

emitter.emit('data', { id: 1, name: 'hello' });
  • Generator 函数

Generator函数是一种可以暂停和恢复执行的函数,可以用于实现异步操作的控制流。通过yield关键字,可以在函数内部暂停执行,并在外部恢复执行。

function* fetchDataGenerator(url) {
    try {
        const response = yield fetch(url);
        const data = yield response.json();
        console.log('数据获取成功:', data);
    } catch (error) {
        console.error('发生错误:', error);
    }
}

const generator = fetchDataGenerator('https://api.example.com/data');
generator.next().value.then(response => {
    return generator.next(response).value;
}).then(data => {
    generator.next(data);
});
  • Web Workers

Web Workers允许你在后台线程中运行JavaScript代码,从而实现多线程处理。这在处理大量数据或执行耗时操作时非常有用,可以避免阻塞主线程。

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

worker.onmessage = (event) => {
    console.log('从Worker接收到数据:', event.data);
};

worker.postMessage('开始处理数据');

// worker.js
self.onmessage = (event) => {
    console.log('接收到消息:', event.data);
    // 处理数据
    const result = { id: 1, name: 'Hello' };
    self.postMessage(result);
};
  • Observables(RxJS)

Observables是Reactive Programming(响应式编程)中的一个概念,用于处理异步数据流。RxJS库提供了丰富的操作符,可以方便地处理复杂的异步操作。

import { fromEvent } from 'rxjs';
import { debounceTime, map } from 'rxjs/operators';

const inputElement = document.getElementById('input');

fromEvent(inputElement, 'input')
    .pipe(
        debounceTime(300),
        map(event => event.target.value)
    )
    .subscribe(value => {
        console.log('输入值:', value);
    });

参考文章: