资讯专栏INFORMATION COLUMN

【译】理解回调和Promise

liuyix / 2447人阅读

摘要:理解回调和原文自工程师博客,传送门这两个概念是编程语言的基本内容。回调地狱就是滥用回调。通常,在回调中,错误作为第一个参数传递。这个具有这两个函数作为参数的回调称为执行程序。到目前为止,我希望我已经让自己了解了回调和。

理解回调和Promise
原文自工程师Fernando Hernandez博客,传送门

这两个概念是Javascript编程语言的基本内容。因为这种语言是在异步编程的范例下工作。

所以,我决定分享这篇文章,以便了解这两个用来执行异步操作的特性——回调和Promise是什么。

那么,我们开始吧!

回调

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

假设我们正在通电话。在谈话时,出现了需要立即解决的情况。我们把电话挂了,我们先做需要立即解决的事情,当我们完成时,我们再回到我们刚刚暂停的电话。

好吧,通过这个例子,我们可以大致了解什么是回调。

现在,用编程语言说。

回调是在异步操作已经完成后将要执行的功能。

回调作为参数传递给异步操作。通常,是作为函数的最后一个参数传递的。这样做是一种很好的做法,所以请记住这一点。

回调的结构如下所示:

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

setTimeout(()=>{sayHello()}, 3000);

我们在上面的例子中所做的是,首先定义一个向控制台输出消息的函数。之后,我们使用一个名为setTimeout的计时器(此计时器是一个本机Javascript函数)。此计时器是一个异步操作,在一定时间后执行回调。在这个例子中,在3000ms(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)
}

养成良好的编程习惯,让其余程序员不会恨你一辈子!

Promise

Javascript中的Promise就是相当于字面意思上的承诺。我们知道,当我们做出承诺时,这意味着我们将尽一切可能实现预期的结果。但是,我们也知道,由于某种原因,不能总是履行承诺。

正如承诺在现实生活中一样,它是在Javascript中,则另一种方式表示即代码。

让我们看一个Promise的例子:

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的构造函数接收一个参数:一个回调,它有两个参数:

resolve

reject

这些是已经在Javascript中定义的函数,因此我们不用自己去构建它们。

这个具有这两个函数作为参数的回调称为执行程序。

执行者在创建承诺时立即运行。

执行函数将执行什么?

好吧,在这里面,我们将放置所有必要的代码来实现我们的承诺。

一旦执行程序完成执行,我们将发送其中一个函数作为参数。

如果实现了,我们使用resolve函数。

如果由于某种原因失败,我们使用reject函数。

函数resolve和reject,只接收一个参数。reject函数通常会使用Error类传递错误,正如我们在前面的示例中所看到的那样。

Promise有三个独特的状态:

Pending:异步操作尚未完成。

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

Rejected:指示异步操作失败以及失败的原因。

Promise对象有两个属性:

State:表示Promise的状态。

Result:存储Promise的值(如果已满足)或错误(如果已拒绝)。

最初,Promise的状态为“pending”,结果为“undefined”。

一旦promise完成执行,promise的状态和结果将被修改为相应的值。取决于promise是否已完成或被拒绝。

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

一旦promise改变了他们的状态,他们就无法逆转。

如何使用或调用Promise?

为了使用我们创建的Promise,我们使用then和catch函数。在代码中,它们看起来像这样:

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

then允许我们处理已完成或已执行状态的promise

函数catch将允许我们处理被拒绝状态的promise

在then函数中,我们也可以处理被拒绝的promise。为此,处理程序接收两个参数。第一个是已完成的promise,第二个是被拒绝的promise。通过这种方式:

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

处理程序then和catch都是异步的。

基本上,一旦Javascript执行了下面的代码,就会执行then和catch。

例:

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

console.log("Hello world")

我们可能认为首先它会先在控制台输出在promise中的value或error。但是要知道它们是异步操作,我们必须记住它将花费最少的时间来执行,因此消息“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

在随后处理程序将在控制台输出每个promise的结果的数组。
如果其中一个promise被reject,则该函数将被reject并出现错误。如下所示:

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先执行。

因此,无论promise是否被拒绝或完成,race方法将执行第一个并忽略其他方法。

到目前为止,我希望我已经让自己了解了回调和promise。基本上,Javascript的这两个特性用于处理异步操作。这就是这门语言的基础,因此它很受欢迎。

我将很快继续关于处理异步的另一篇文章——Async-Await。

译者总结

本文是小编的第一次译文,翻译不到位请见谅。由于突然想重温一下Promise,为此对Promise的知识点进行了再次温故,看看从不同人的角度怎么去理解Promise的。上文对Promise进行了简单的介绍并附带一些回调的知识点,也让我对回调有了新的见解。相信对读者也会有所帮助,我会再接再厉的!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101823.html

相关文章

  • [] 深入理解 Promise 五部曲:2. 控制权转换问题

    摘要:直到最近,我们仍然在用简单的回调函数来处理异步的问题。当我们只有一个异步任务的时候使用回调函数看起来还不会有什么问题。 原文地址:http://blog.getify.com/promis... 厦门旅行归来,继续理解Promise 在上一篇深入理解Promise五部曲:1.异步问题中,我们揭示了JS的异步事件轮询并发模型并且解释了多任务是如何相互穿插使得它们看起来像是同时运行的。...

    alanoddsoff 评论0 收藏0
  • [] 深入理解 Promise 五部曲:1. 异步问题

    摘要:当引擎开始执行一个函数比如回调函数时,它就会把这个函数执行完,也就是说只有执行完这段代码才会继续执行后面的代码。当条件允许时,回调函数就会被运行。现在,返回去执行注册的那个回调函数。 原文地址:http://blog.getify.com/promis... 在微博上看到有人分享LabJS作者写的关于Promise的博客,看了下觉得写得很好,分五个部分讲解了Promise的来龙去脉。从...

    CHENGKANG 评论0 收藏0
  • 理解Node事件驱动架构

    摘要:回调方式将回调函数作为参数传递给主函数,同时在主函数内部处理错误信息。模块是促进中对象之间交流的模块,它是异步事件驱动机制的核心。在异步函数的回调中,根据执行情况触发或者事件。比如,当异常事件触发关闭数据库的动作时。 原文链接:Understanding Nodejs Event-driven Architecture 作者:Samer Buna 翻译:野草 本文首发于前端早读课【...

    mrcode 评论0 收藏0
  • []理解 Node.js 事件驱动机制

    摘要:事件驱动机制的最简单形式,是在中十分流行的回调函数,例如。在回调函数这种形式中,事件每被触发一次,回调就会被触发一次。回调函数需要作为宿主函数的一个参数进行传递多个宿主回调进行嵌套就形成了回调地狱,而且错误和成功都只能在其中进行处理。 学习 Node.js 一定要理解的内容之一,文中主要涉及到了 EventEmitter 的使用和一些异步情况的处理,比较偏基础,值得一读。 阅读原文 大...

    Snailclimb 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0

发表评论

0条评论

liuyix

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<