理解回调和承诺 回调 承诺

2025-06-10

理解回调和承诺

回调

承诺

这两个概念是 JavaScript 编程语言的基本概念。因为该语言是在异步编程范式下工作的。

因此,我决定分享这篇文章,让大家了解回调和承诺的含义。这两个特性可以用来执行异步操作。

那么,我们走吧👍

回调

为了理解回调,我将做一个简单的类比。

假设我们正在打电话。通话过程中,出现了需要立即解决的情况。我们暂停通话,处理需要处理的事情,处理完之后再继续之前暂停的通话。

好了,通过这个例子,我们可以大致了解一下什么是回调。基本上,正如名字所示。

现在,用编程语言来讲。

回调是异步操作完成后执行的函数。

回调函数作为参数传递给异步操作。通常,它会作为函数的最后一个参数传递。这是一个好习惯,请记住这一点。

回调结构如下所示:

function sayHello() {
    console.log('Hello everyone');
}

setTimeout(sayHello(), 3000)

在上面的例子中,我们首先定义了一个函数,用于将消息打印到控制台。然后,我们使用了一个名为setTimeout的计时器(这是一个原生的 JavaScript 函数)。这个计时器是一个异步操作,会在一定时间后执行回调函数。在这个例子中,3000 毫秒(3 秒)后将执行 sayHello 函数。

回调模式

正如我们在开头提到的,作为优秀的开发人员,我们应该尊重回调函数的位置,将其作为参数。回调函数应该始终放在最后一个参数。这被称为回调模式。

这样,我们的代码将更具可读性,并且其他程序员在处理它时也更容易维护。

我们来看另一个回调示例:

const fs = require('fs') // Importing Nodejs library

// Declaring file path
const filePath = './users.json'

// Asynchronous operation to read the file
fs.readFile(filePath, function onReadFile(err, result) {
    // In case of error print it in the console
    if (err) {
        console.log('There was an error: ' + err)
        return // Get out of the function
    }
    // Print on the console the file and the content of it.
    console.log('The file was successfully read it: ' + result)
})

这里,我们使用了一个 Nodejs 库来操作文件系统。示例中,我们使用 readFile 函数来从计算机读取文件。该函数接收两个参数(文件路径和回调函数)。我们注意到,名为 onReadFile 的回调函数位于最后一个参数中。

回调通常以匿名方式声明,但最好为其分配一个名称,以便在发生错误时更容易识别它。

最后,该回调将一直执行,直到我们的代码完成读取请求的文件。如果存在,JavaScript 将在此过程中继续执行代码。


回调地狱

一旦你了解了回调的工作原理并将其付诸实践,我们必须记住一些事情。作为一名优秀的开发人员,我们必须知道如何使用它,并避免像回调地狱这样的糟糕情况。

回调地狱是回调的误用。它看起来像这样:

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename,             function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

基本上,使用嵌套回调是一种不好的做法,正如我们所见,它会在视觉上产生一种金字塔形状。这会使代码难以维护和阅读,而我们不希望出现这种情况。

如何避免回调地狱?

  • 命名函数:正如我之前所说,您可以做的第一件事就是命名您的函数(回调)。这样,当生成错误时,它会通过函数名称以特定的方式指示错误。此外,这还能让您的代码更具描述性,其他程序员阅读时也更容易维护。

  • 模块化:命名函数后,就可以开始单独定义它们了。这样,你只需要添加回调函数的名称。首先,先在同一个文件(文件底部)中定义它们。然后,另一个选择是将该函数写在单独的文件中。这样,我们就可以在任意文件中导出和导入它了。

这使得代码可重用、可读性更高、维护更方便。

  • 处理错误:编写代码时,我们必须牢记错误随时可能发生。为了能够轻松识别错误,编写处理可能发生错误的代码非常重要。

在回调中,错误通常作为第一个参数传递。我们可以按以下方式处理错误:

const fs = require('fs')

const filePath = './users.json'

fs.readFile(filePath, handleFile)

function handleFile(err, result) {
    if (err) {
        return console.log('There was an error: ' + err)
    }
    console.log('File: ' + result)
}

应用良好的代码实践,让其他程序员一辈子都不会恨你!


承诺

承诺图像

JavaScript 中的 Promise 就是 Promise。我们知道,当我们做出 Promise 时,意味着我们将竭尽所能实现预期的结果。但是,我们也知道,由于某些原因,Promise 并不总是能够实现。

正如现实生活中的承诺一样,它在 Javascript 中也以另一种方式表示:在代码中。

让我们看一个承诺的例子:

let promise = new Promise(function(resolve, reject) {
    // things to do to accomplish your promise

    if(/* everything turned out fine */) {
        resolve('Stuff worked')
    } else { // for some reason the promise doesn't fulfilled
        reject(new Error('it broke'))
    }
})

Promise 是 Javascript 的一个原生类(自 ES6 起)。

Promise 的构造函数接收一个参数:一个回调,它有两个参数:

  • 解决
  • 拒绝

这些是已经在 J​​avascript 中定义的函数,因此我们不应该自己构建它们。

这个以这两个函数作为参数的回调被称为执行器。

当承诺被创建时,执行器立即运行。

这个执行器函数要执行什么?

好吧,在这其中,我们将放入实现我们的承诺所需的所有代码。

一旦执行器完成执行,我们将发送它的一个函数作为参数。

  • 如果满足,我们将使用解析函数。

  • 如果由于某种原因失败,我们会使用拒绝功能。

resolve 和 rejection 函数只接收一个参数。reject 函数通常会传递 Error 类的错误信息,就像我们在前面的例子中看到的那样。

Promise 有三种独特的状态:

  • Pending:异步操作尚未完成。

  • Fulfilled:异步操作已完成并返回一个值。

  • Rejected:异步操作失败,并指出失败的原因。

promise 对象有两个属性:

  • 状态:表示承诺的状态。
  • 结果:如果承诺已实现,则存储承诺的值;如果承诺被拒绝,则存储错误。

最初,承诺的状态为“待定”,结果为“未定义”。

一旦 Promise 执行完毕,其状态和结果将被修改为相应的值,具体取决于 Promise 是已完成还是被拒绝。

让我们看下面的图表来更好地理解它:

兑现承诺

承诺被拒绝

一旦承诺的状态发生变化,就无法撤销。

如何使用或者调用承诺?

要使用我们创建的 Promise,我们需要使用 then 和 catch 函数。代码如下:

promise.then(function(result) {
    console.log(result)
}).catch(function(err) {
    console.log(err)
})

该函数允许我们处理已完成或已实现的承诺。

函数catch将允许我们处理被拒绝的承诺。

在then函数中,我们还可以处理被拒绝的 Promise。为此,处理程序接收两个参数。第一个参数用于处理 Promise 已实现的情况,第二个参数用于处理 Promise 被拒绝的情况。如下所示:

promise.then(function(result) { // Handling the value
    console.log(result)
}, function(err) { // Handling the error
    console.log(err)
})

thencatch处理程序是异步

基本上,一旦 Javascript 读取完下面的代码, thencatch就会被执行。

例子:

promise.then(function(result) {
    console.log(result)
}).catch(function(err) {
    console.log(err)
})

console.log('Hello world')

我们可以认为它会首先打印在 Promise 的值或错误中。但由于它们是异步操作,我们必须记住,执行它需要最少的时间,因此会首先显示“Hello world”消息。


Promise 类有一个名为all的方法,用于执行一组 Promise。它看起来像这样:

Promise.all([
    new Promise.((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
    new Promise.((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
    new Promise.((resolve, reject) => setTimeout(() => resolve(3), 1000)), // 3
]).then(result => console.log(result)) // 1, 2, 3

then处理程序将在控制台中打印每个 Promise 的结果数组。
如果其中一个 Promise 被拒绝,则此函数将被拒绝并返回错误。如下图所示:

Promise.all([
    new Promise.((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1
    new Promise.((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2
    new Promise.((resolve, reject) => setTimeout(() => reject(new Error('An error has ocurred')), 1000))
]).then(result => console.log(result))
.catch(err => console.log(err)) // An error has ocurred

还有一种与all类似但略有不同的方法,那就是race方法。

与all函数相同,它接收一个 Promise 数组,但它会返回最先完成或被拒绝的 Promise。我们来看一个代码示例:

let promise1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('promise one')
    }, 3000) // Resolve after 3 seconds
})

let promise2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('promise two')
    }, 1000) // Resolve after 1 seconds
})

Promise.race([
    promise1,
    promise2
]).then(result => console.log(result)) // promise two

可以看到,返回的值只是第二个 Promise 的响应。这是因为第二个 Promise 先被执行了。
我们再来看一个被拒绝的 Promise 的例子:

let promise1 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('promise one')
    }, 3000) // Resolve after 3 seconds
})

let promise2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('promise two')
    }, 2000) // Resolve after 2 seconds
})

let promise3 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        reject('promise three rejected')
    }, 1000) // Reject after 1 second
})

Promise.race([
    promise1,
    promise2,
    promise3
]).then(result => console.log(result))
.catch(err => console.log(err)) // promise three is rejected

在这段代码中,race函数将打印出它在我们声明的第三个 Promise 中发现的错误。你应该已经能想象出原因了。实际上,第三个 Promise 比其他 Promise 先执行。

因此,无论承诺是被拒绝还是完成,race方法都会执行第一个并忽略其他承诺。


到目前为止,我希望我已经理解了回调和 Promise。基本上,JavaScript 的这两个特性用于处理异步操作。这门语言正是基于此,因此才如此受欢迎。

我很快会写另一篇文章来介绍处理异步的最后一个功能:Async-Await。

鏂囩珷鏉ユ簮锛�https://dev.to/_ferh97/understanding-callbacks-and-promises-3fd5
PREV
如何编写干净的代码
NEXT
6 个将代码片段转换为图像的超棒工具