Javascript高级编程基础之从回调地狱到Async/Await_Promise

Javascript高级编程基础之从回调地狱到Async/Await_Javascript_02

Javascript高级编程基础之从回调地狱到Async/Await_Javascript_03

一、Javascript中回调函数的功能

在JavaScript中,回调函数(Callback Function)是一种通过函数指针调用的函数,通常作为参数传递给其他函数,以便在某个特定事件发生时被调用。回调函数在JavaScript中扮演着非常重要的角色,尤其是在处理异步操作、事件监听、定时任务以及需要后续处理的场景中。

以下是回调函数在JavaScript中的一些主要功能:


  •    异步处理:JavaScript中的很多操作是异步的,比如网络请求、文件读写等。通过回调函数,我们可以在这些异步操作完成后执行特定的代码。例如,在发起Ajax请求时,我们可以提供一个回调函数来处理服务器返回的数据。
  • 事件监听:在Web开发中,我们经常需要监听各种事件,如点击、鼠标移动、键盘输入等。通过为这些事件注册回调函数,我们可以在事件发生时执行相应的代码。
  • 定时任务:使用setTimeout和setInterval函数时,我们可以提供回调函数来指定在特定时间后或每隔一段时间执行的操作。
  • 数组操作:一些数组方法,如forEach、map、filter等,接受回调函数作为参数,以便对数组中的每个元素执行特定的操作。
  • 错误处理:在Node.js等环境中,很多异步操作都提供了错误优先的回调函数(error-first callback),这样我们可以在操作失败时捕获并处理错误。
  • 模块化与解耦:回调函数有助于实现模块化和解耦。通过将功能划分为不同的模块,并使用回调函数进行通信,我们可以构建出更加灵活和可维护的代码结构。
  • 流程控制:在某些复杂的异步操作中,我们可能需要按顺序执行多个任务。通过回调函数,我们可以实现这种流程控制,确保一个任务完成后才执行下一个任务。

下面是一个简单的回调函数示例:

function greet(name, callback) {  
  console.log(`Hello, ${name}!`);  
  callback();  
}  
  
function sayGoodbye() {  
  console.log("Goodbye!");  
}  
  
greet("Alice", sayGoodbye);

在这个例子中,greet函数接受一个名字和一个回调函数作为参数。在打印出问候语后,它调用提供的回调函数sayGoodbye来打印告别语。这种方式展示了回调函数如何用于在特定操作完成后执行后续操作。

二、Javascript中的回调地狱

在JavaScript中,"回调地狱"(Callback Hell)是一个用来描述由于过度嵌套回调函数而导致的代码结构混乱和难以管理的问题。当你在处理多个异步操作时,比如从数据库获取数据、读取文件、进行网络请求等,你经常需要在一个回调函数中调用另一个回调函数,这样一层层嵌套下去,就形成了所谓的“回调地狱”。

以下是一个简单的“回调地狱”示例:

doSomething(function(result) {  
  doSomethingElse(result, function(newResult) {  
    doThirdThing(newResult, function(finalResult) {  
      console.log('Got the final result: ' + finalResult);  
    }, failureCallback);  
  }, failureCallback);  
}, failureCallback);


在上述代码中,每个函数都依赖于前一个函数的结果,并且我们有一个failureCallback来处理任何可能发生的错误。随着嵌套层级的增加,代码的可读性和可维护性会迅速降低。

为了解决这个问题,有几种方法可以改善回调地狱的情况:

   Promise:

  • 使用Promise可以将异步操作串联起来,而无需深度嵌套。Promise提供了一个then方法来处理成功的结果,以及一个catch方法来处理错误。这使得代码更加线性和可读。

Promise是在ECMAScript 2015(也称为ES6)版本中引入的。这一新特性旨在解决异步编程中的回调地狱问题,使异步代码更加清晰和易于管理。通过Promise,开发者可以更加优雅地处理异步操作的成功与失败情况,提高代码的可读性和可维护性。

doSomething()  
  .then(result => doSomethingElse(result))  
  .then(newResult => doThirdThing(newResult))  
  .then(finalResult => {  
    console.log('Got the final result: ' + finalResult);  
  })  
  .catch(failureCallback);

   Async/Await:

  • ES2017引入了async/await语法,它使得异步代码的书写更加直观和易于理解。async函数允许你使用await关键字等待Promise解决,这样你就可以用同步的方式编写异步代码。


async function asyncFunction() {  
  try {  
    const result = await doSomething();  
    const newResult = await doSomethingElse(result);  
    const finalResult = await doThirdThing(newResult);  
    console.log('Got the final result: ' + finalResult);  
  } catch (error) {  
    failureCallback(error);  
  }  
}


使用Promises或Async/Await可以显著减少回调地狱问题,使代码更加清晰和易于维护。在现代JavaScript开发中,推荐使用这些方法来处理异步操作。

三、JavaScript中Promise的使用

在JavaScript中,Promise 是一种用于处理异步操作的对象,它代表了某个尚未完成的操作的最终完成(或失败)及其结果值。使用 Promise 可以让你更加优雅地组织和管理异步代码,避免回调地狱(callback hell)的问题。

以下是使用 Promise 的一般思路:

创建Promise对象:

   你可以使用 new Promise() 构造函数来创建一个新的Promise对象。这个构造函数接受一个函数作为参数,这个函数有两个参数:resolve 和 reject,分别用于表示异步操作成功和失败时的回调函数。    

const promise = new Promise((resolve, reject) => {  
        // 模拟异步操作,比如网络请求、定时器等  
        setTimeout(() => {  
            if (/* 异步操作成功 */) {  
                resolve('操作成功的结果');  
            } else {  
                reject('操作失败的原因');  
            }  
        }, 1000);  
    });


处理Promise的结果:

   使用 .then() 和 .catch() 方法来处理Promise的结果。.then() 方法接受一个回调函数作为参数,这个函数将在Promise被解决(resolved)时调用,并接收Promise的结果作为参数。.catch() 方法也接受一个回调函数,该函数将在Promise被拒绝(rejected)时调用,并接收拒绝的原因作为参数。  

promise  
        .then(result => {  
            console.log(result); // 输出操作成功的结果  
        })  
        .catch(error => {  
            console.error(error); // 输出操作失败的原因  
        });


链式调用:

   你可以将 .then() 和 .catch() 链接在一起,以便按顺序执行多个异步操作,并将前一个操作的结果传递给下一个操作。这被称为Promise链。  

firstAsyncOperation()  
        .then(result => {  
            // 处理第一个异步操作的结果  
            return secondAsyncOperation(result);  
        })  
        .then(secondResult => {  
            // 处理第二个异步操作的结果  
        })  
        .catch(error => {  
            // 处理任何异步操作中的错误  
        });

    Promise.all():

   如果你需要等待多个Promise都完成后才继续执行,可以使用 Promise.all() 方法。这个方法接受一个Promise对象的数组作为参数,并返回一个新的Promise对象,该对象在所有输入的Promise都成功解决后才会解决,并返回一个包含所有Promise结果的数组。    

Promise.all([promise1, promise2, promise3])  
        .then(results => {  
            // results是一个数组,包含promise1、promise2和promise3的结果  
        })  
        .catch(error => {  
            // 处理任何一个Promise失败的情况  
        });


async/await:

   ES2017引入了 async/await 语法糖,它使得异步代码的书写和理解更加直观。在 async 函数中,你可以使用 await 关键字来等待一个Promise的解决,而不需要显式地使用 .then() 或 .catch()。

   

async function main() {  
        try {  
            const result1 = await firstAsyncOperation();  
            const result2 = await secondAsyncOperation(result1);  
            // 处理result2  
        } catch (error) {  
            // 处理错误  
        }  
    }  
    main();


总的来说,Promise提供了一种更加结构化和可维护的方式来处理JavaScript中的异步操作。通过合理地使用Promise,你可以避免回调地狱,使代码更加清晰和易于理解。

四、JavaScript开源世界中的Bluebird库

Bluebird库是一个功能强大的Promise库,它为JavaScript提供了增强的Promise实现


[提示]在JavaScript世界中,由于涉及到不同版本JS创建的开源库,所以,会不同程度地涉及到本文中几种异步方式的转换问题。例如,在知名的网易开源游戏服务器框架Pomelo(最新更名为基于Typescript版本的Pinus)。在Pinus框架中,就把早期的回调函数方式更新为基于Bluebird库的Promise调用方式。


以下是在JavaScript开发中使用Bluebird库的基本方法:

1. 安装Bluebird

首先,你需要将Bluebird库添加到你的项目中。这通常通过npm(Node Package Manager)来完成:

npm install bluebird


2. 引入Bluebird

在你的JavaScript文件中,你需要使用require来引入Bluebird库:

const Promise = require('bluebird');


或者,如果你使用ES6模块语法,可以这样引入:

import Promise from 'bluebird';

3. 使用Bluebird的Promise


一旦你引入了Bluebird,你就可以开始使用它提供的Promise功能了。以下是一些基本用法:

创建Promise

const promise = new Promise((resolve, reject) => {  
    // 模拟异步操作  
    setTimeout(() => {  
        if (/* 某些条件 */) {  
            resolve('操作成功');  
        } else {  
            reject('操作失败');  
        }  
    }, 1000);  
});


处理Promise


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


Promisify现有的回调函数API

Bluebird还提供了一个非常有用的功能,即可以将现有的基于回调的API转换为返回Promise的API。例如:

const fs = require('fs');  
const Promise = require('bluebird');  
  
// Promisify fs模块的所有方法  
const fsPromises = Promise.promisifyAll(fs);  
  
// 使用promisified的readFile方法  
fsPromises.readFileAsync('example.txt', 'utf8')  
    .then(data => {  
        console.log(data);  
    })  
    .catch(error => {  
        console.error(error);  
    });


使用Promise.map进行并行处理

Bluebird提供了Promise.map方法来并行处理数组中的每个元素,并返回一个Promise数组:

const promiseArray = [1, 2, 3].map(num =>   
    new Promise((resolve, reject) => {  
        setTimeout(() => resolve(num * 2), 1000);  
    })  
);  
  
Promise.map(promiseArray, result => {  
    console.log(result); // 输出每个promise的结果  
    return result * 2; // 对每个结果进行进一步处理  
}, { concurrency: 2 }) // 控制并行处理的数量  
.then(results => {  
    console.log(results); // 输出最终处理后的结果数组  
});


使用Promise.coroutine简化异步流程

Bluebird还支持使用Promise.coroutine来简化基于Promise的异步流程,使其看起来像同步代码一样:

const coroutine = Promise.coroutine;  
  
const myCoroutine = coroutine(function* () {  
    try {  
        const result1 = yield someAsyncOperation1();  
        const result2 = yield someAsyncOperation2(result1);  
        const finalResult = yield someAsyncOperation3(result2);  
        return finalResult;  
    } catch (error) {  
        console.error(error);  
    }  
});  
  
myCoroutine().then(result => {  
    console.log(result);  
});


请注意,随着JavaScript的发展,原生async/await语法已经成为了处理异步流程的主流方式,它在很多方面与Promise.coroutine相似,但更加直观和简洁。因此,在新项目中,你可能会更倾向于使用async/await而不是Bluebird的coroutine。


总的来说,Bluebird库提供了丰富的Promise功能,可以帮助你更加优雅和高效地处理JavaScript中的异步操作。不过,在使用之前,请确保你了解Promise的基本概念和工作原理。