共计 6303 个字符,预计需要花费 16 分钟才能阅读完成。
理解回调和 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 进行了简单的介绍并附带一些回调的知识点,也让我对回调有了新的见解。相信对读者也会有所帮助,我会再接再厉的!