MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript async函数的返回值处理

2021-10-133.4k 阅读

JavaScript async 函数的返回值处理

一、async 函数基础回顾

在深入探讨 async 函数返回值处理之前,我们先来简要回顾一下 async 函数的基本概念。在 JavaScript 中,async 函数是一种异步函数,它返回一个 Promise 对象。当 async 函数执行时,如果其返回值不是一个 Promise,JavaScript 会自动将其包装成一个已解决(resolved)状态的 Promise

例如:

async function simpleAsyncFunction() {
    return 42;
}

simpleAsyncFunction().then(value => {
    console.log(value); // 输出: 42
});

在上述代码中,simpleAsyncFunction 是一个 async 函数,它返回数字 42。由于 42 不是 Promise,JavaScript 引擎将其包装成一个已解决状态的 Promise,其 resolve 值为 42。所以通过 .then 可以获取到这个值。

二、正常返回值的处理

  1. 返回基本数据类型async 函数返回基本数据类型,如字符串、数字、布尔值等,就像前面例子中返回数字 42 一样,JavaScript 会将其包装成 Promise。这使得我们可以像处理普通 Promise 一样,使用 .then 方法来获取这个值。
    async function returnString() {
        return 'Hello, async!';
    }
    
    returnString().then(str => {
        console.log(str); // 输出: Hello, async!
    });
    
  2. 返回对象 async 函数也可以返回对象。同样,该对象会被包装成一个已解决状态的 Promise
    async function returnObject() {
        return {name: 'John', age: 30};
    }
    
    returnObject().then(obj => {
        console.log(obj.name); // 输出: John
        console.log(obj.age);  // 输出: 30
    });
    
  3. 返回数组 数组作为返回值也遵循相同的规则。
    async function returnArray() {
        return [1, 2, 3];
    }
    
    returnArray().then(arr => {
        console.log(arr[0]); // 输出: 1
    });
    

三、返回 Promise 对象的处理

  1. 返回已解决状态的 Promise async 函数可以直接返回一个已解决状态的 Promise。在这种情况下,async 函数的行为与直接返回值类似,只是 Promise 已经是外部创建好的。
    async function returnResolvedPromise() {
        return Promise.resolve('Resolved value');
    }
    
    returnResolvedPromise().then(value => {
        console.log(value); // 输出: Resolved value
    });
    
    这里 returnResolvedPromise 函数返回了一个已解决的 Promiseasync 函数会直接传递这个 Promise 的解决值,就好像它是直接返回的普通值一样。
  2. 返回已拒绝状态的 Promiseasync 函数返回一个已拒绝状态的 Promise 时,我们可以通过 .catch 方法来捕获拒绝原因。
    async function returnRejectedPromise() {
        return Promise.reject('Rejection reason');
    }
    
    returnRejectedPromise().catch(reason => {
        console.log(reason); // 输出: Rejection reason
    });
    
    这种情况下,async 函数返回的 Promise 会直接进入拒绝状态,并且将拒绝原因传递给 .catch 块。
  3. 处理复杂的 Promise 链返回 async 函数内部可能涉及复杂的 Promise 链操作,最后返回一个 Promise
    async function complexPromiseChain() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('First resolve');
            }, 1000);
        })
       .then(value => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    resolve(value +'and Second resolve');
                }, 1000);
            });
        });
    }
    
    complexPromiseChain().then(finalValue => {
        console.log(finalValue); // 输出: First resolve and Second resolve
    });
    
    在这个例子中,complexPromiseChain 函数内部创建了一个 Promise 链,第一个 Promise 在 1 秒后解决,然后其解决值作为第二个 Promise 的输入,第二个 Promise 又在 1 秒后解决。最终 async 函数返回的是整个 Promise 链的最终解决值。

四、错误处理与返回值

  1. 使用 try - catch 捕获错误async 函数内部,如果发生错误,可以使用传统的 try - catch 块来捕获。
    async function errorInAsyncFunction() {
        try {
            throw new Error('Internal error');
        } catch (error) {
            console.log('Caught error:', error.message);
            return 'Error handled, returning a value';
        }
    }
    
    errorInAsyncFunction().then(value => {
        console.log(value); // 输出: Error handled, returning a value
    });
    
    在上述代码中,async 函数内部抛出了一个错误,通过 try - catch 捕获后,catch 块中可以进行错误处理,并返回一个值。这个返回值同样会被包装成已解决状态的 Promise
  2. 未捕获错误导致 Promise 拒绝 如果 async 函数内部发生错误,且没有被 try - catch 捕获,那么返回的 Promise 会进入拒绝状态。
    async function uncaughtErrorInAsyncFunction() {
        throw new Error('Uncaught error');
    }
    
    uncaughtErrorInAsyncFunction().catch(error => {
        console.log('Caught uncaught error:', error.message);
    });
    
    这里 async 函数直接抛出了一个错误,没有使用 try - catch 捕获,所以返回的 Promise 进入拒绝状态,通过 .catch 可以捕获到这个错误。
  3. 处理异步操作中的错误async 函数内部执行异步操作(如 fetch)发生错误时,同样可以通过 try - catch.catch 来处理。
    async function asyncOperationWithError() {
        try {
            const response = await fetch('https://nonexistenturl.com');
            const data = await response.json();
            return data;
        } catch (error) {
            console.log('Error in fetch:', error.message);
            return null;
        }
    }
    
    asyncOperationWithError().then(data => {
        console.log(data); // 如果发生错误,输出 null
    });
    
    在这个 fetch 操作的例子中,如果 fetch 的 URL 不存在,会抛出错误,try - catch 捕获并处理错误,返回 null。如果不使用 try - catch,则需要在调用 asyncOperationWithError 函数的地方通过 .catch 来捕获错误。

五、多个 async 函数的返回值处理

  1. 并行执行多个 async 函数 使用 Promise.all 可以并行执行多个 async 函数,并处理它们的返回值。
    async function asyncFunction1() {
        return 'Result of asyncFunction1';
    }
    
    async function asyncFunction2() {
        return 'Result of asyncFunction2';
    }
    
    async function parallelExecution() {
        const results = await Promise.all([asyncFunction1(), asyncFunction2()]);
        console.log(results[0]); // 输出: Result of asyncFunction1
        console.log(results[1]); // 输出: Result of asyncFunction2
    }
    
    parallelExecution();
    
    Promise.all 接受一个 Promise 数组(这里 async 函数返回的都是 Promise),当所有 Promise 都解决时,Promise.all 返回的 Promise 才会解决,其解决值是一个包含所有 Promise 解决值的数组。
  2. 串行执行多个 async 函数 可以通过链式调用 async 函数来实现串行执行。
    async function asyncFunctionA() {
        return 'Result of asyncFunctionA';
    }
    
    async function asyncFunctionB(resultA) {
        return resultA +'and Result of asyncFunctionB';
    }
    
    async function serialExecution() {
        const resultA = await asyncFunctionA();
        const finalResult = await asyncFunctionB(resultA);
        console.log(finalResult); // 输出: Result of asyncFunctionA and Result of asyncFunctionB
    }
    
    serialExecution();
    
    在这个例子中,asyncFunctionA 先执行,其返回值作为 asyncFunctionB 的输入,从而实现了串行执行。
  3. 处理部分失败的情况(Promise.race 和 Promise.allSettled)
    • Promise.racePromise.race 接受一个 Promise 数组,只要数组中的任何一个 Promise 解决或拒绝,Promise.race 返回的 Promise 就会解决或拒绝。
    async function asyncFunctionFast() {
        return 'Fast result';
    }
    
    async function asyncFunctionSlow() {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve('Slow result');
            }, 2000);
        });
    }
    
    async function raceExecution() {
        const result = await Promise.race([asyncFunctionFast(), asyncFunctionSlow()]);
        console.log(result); // 输出: Fast result
    }
    
    raceExecution();
    
    • Promise.allSettledPromise.allSettled 接受一个 Promise 数组,当所有 Promise 都已解决(resolved)或已拒绝(rejected)时,Promise.allSettled 返回的 Promise 才会解决,其解决值是一个包含每个 Promise 结果(无论是解决还是拒绝)的数组。
    async function asyncFunctionResolved() {
        return 'Resolved value';
    }
    
    async function asyncFunctionRejected() {
        throw new Error('Rejected reason');
    }
    
    async function allSettledExecution() {
        const results = await Promise.allSettled([asyncFunctionResolved(), asyncFunctionRejected()]);
        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Promise ${index} resolved with value:`, result.value);
            } else {
                console.log(`Promise ${index} rejected with reason:`, result.reason);
            }
        });
    }
    
    allSettledExecution();
    
    在这个例子中,Promise.allSettled 可以获取到每个 async 函数返回的 Promise 的最终状态,无论是解决还是拒绝。

六、与其他异步机制结合的返回值处理

  1. 与 setTimeout 结合 async 函数常与 setTimeout 结合使用,以模拟异步操作。
    async function asyncWithSetTimeout() {
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve('Timeout resolved');
            }, 1500);
        });
    }
    
    asyncWithSetTimeout().then(value => {
        console.log(value); // 输出: Timeout resolved
    });
    
    这里 async 函数内部使用 setTimeout 创建了一个异步操作,1.5 秒后 Promise 解决,返回值可以通过 .then 获取。
  2. 与回调函数结合(将回调转换为 Promise) 在 JavaScript 中,有些旧的 API 仍然使用回调函数。我们可以将回调函数转换为 Promise,并在 async 函数中使用。
    function oldCallbackAPI(callback) {
        setTimeout(() => {
            callback(null, 'Callback result');
        }, 1000);
    }
    
    function convertToPromise() {
        return new Promise((resolve, reject) => {
            oldCallbackAPI((error, result) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(result);
                }
            });
        });
    }
    
    async function useConvertedPromise() {
        const result = await convertToPromise();
        console.log(result); // 输出: Callback result
    }
    
    useConvertedPromise();
    
    在这个例子中,oldCallbackAPI 是一个使用回调的旧 API,convertToPromise 函数将其转换为 Promise,然后在 async 函数 useConvertedPromise 中可以方便地使用。
  3. 与 Generator 函数结合(历史渊源与过渡)async 函数出现之前,Generator 函数是处理异步操作的一种方式。虽然现在 async 函数更常用,但了解它们之间的关系有助于深入理解异步编程。
    function* generatorFunction() {
        yield 1;
        yield 2;
        yield 3;
    }
    
    function convertGeneratorToAsync(generator) {
        return new Promise((resolve, reject) => {
            const iterator = generator();
            function step(value) {
                const result = iterator.next(value);
                if (result.done) {
                    resolve(result.value);
                } else {
                    Promise.resolve(result.value).then(nextValue => {
                        step(nextValue);
                    }).catch(error => {
                        reject(error);
                    });
                }
            }
            step();
        });
    }
    
    async function useGeneratorAsync() {
        const result = await convertGeneratorToAsync(generatorFunction());
        console.log(result); // 输出: undefined(因为最后没有返回值)
    }
    
    useGeneratorAsync();
    
    在这个例子中,generatorFunction 是一个 Generator 函数,convertGeneratorToAsync 函数将其转换为可以通过 async 函数使用的形式。async 函数可以看作是 Generator 函数的语法糖,它更简洁、易用,并且内置了对 Promise 的支持。

七、实际应用场景中的返回值处理

  1. 数据获取与处理 在前端开发中,经常需要从 API 获取数据并进行处理。
    async function fetchUserData() {
        try {
            const response = await fetch('https://example.com/api/user');
            const data = await response.json();
            return data.name;
        } catch (error) {
            console.log('Error fetching user data:', error.message);
            return null;
        }
    }
    
    fetchUserData().then(name => {
        if (name) {
            console.log('User name:', name);
        }
    });
    
    这里 fetchUserData 函数通过 fetch 获取用户数据,将其解析为 JSON 格式,并返回用户名。如果发生错误,返回 null
  2. 文件操作(Node.js 环境) 在 Node.js 中,async 函数常用于文件操作。
    const fs = require('fs');
    const util = require('util');
    
    async function readFileAsync() {
        try {
            const data = await util.promisify(fs.readFile)('example.txt', 'utf8');
            return data;
        } catch (error) {
            console.log('Error reading file:', error.message);
            return null;
        }
    }
    
    readFileAsync().then(content => {
        if (content) {
            console.log('File content:', content);
        }
    });
    
    在这个例子中,readFileAsync 函数使用 util.promisify 将 Node.js 的回调式 fs.readFile 方法转换为 Promise 形式,读取文件内容并返回。如果发生错误,返回 null
  3. 数据库操作(以 MongoDB 为例) 在使用 MongoDB 进行数据库操作时,async 函数也很常用。
    const { MongoClient } = require('mongodb');
    
    async function findUserInDB() {
        const uri = "mongodb://localhost:27017";
        const client = new MongoClient(uri);
        try {
            await client.connect();
            const database = client.db('test');
            const users = database.collection('users');
            const user = await users.findOne({name: 'John'});
            return user;
        } catch (error) {
            console.log('Error querying database:', error.message);
            return null;
        } finally {
            await client.close();
        }
    }
    
    findUserInDB().then(user => {
        if (user) {
            console.log('User found in database:', user);
        }
    });
    
    在这个 MongoDB 操作的例子中,findUserInDB 函数连接到数据库,查询名为 John 的用户并返回。如果发生错误,返回 null。最后通过 finally 块关闭数据库连接。

通过以上对 async 函数返回值处理的全面深入探讨,我们可以更好地在各种异步编程场景中使用 async 函数,处理其返回值,以实现健壮、高效的 JavaScript 代码。无论是简单的数据返回,还是复杂的异步操作组合,都能通过合适的方法进行有效的处理。