如何使用 JavaScript Promises 摆脱回调地狱
什么是回调地狱?Promises 又是什么?要深入了解这些问题,需要对 JavaScript 调用栈有一些基本的了解,所以我会先简单介绍一下,然后引导你走出回调地狱。
野兽的本性
JavaScript 是一种单线程语言 - 这意味着它有一个调用堆栈并且一次只能执行一行代码。
调用栈本质上是一个数据结构,用于跟踪程序下一步应该运行的内容。它遵循 FIFO(先进先出)的规则。
进入一个函数调用,它会被添加到堆栈的顶部,返回一个函数,它会从堆栈的顶部弹出。
你不会去抓最底层的那块华夫饼。JavaScript 也一样。
是的,Javascipt 只有一个调用栈。这实际上简化了代码编写,因为你不必担心并发问题——或者同时发生的多个计算。
伟大的!
……除非你确实希望所有操作同时发生。例如,编写需要对网络进行数十次异步调用的 Web 应用程序时,你肯定不希望为了等待响应而暂停其余代码的执行。这种情况被称为“事件循环”或“主线程”阻塞。
回调地狱
解决 JavaScript 单线程问题的第一个解决方案是将函数嵌套为回调。
它可以完成工作,但确定当前范围和可用变量可能会极具挑战性且令人沮丧。
它让你感觉:

当你有太多嵌套函数时,你会发现自己迷失在迷雾之中——这就是所谓的回调地狱。它很可怕,没有人想去那里!
嵌套回调往往会形成独特的金字塔形状 -
fightTheDemogorgon(function(result) {
rollForDamage(result, function(seasonsLeft) {
closeTheGate(seasonsLeft, function(finalResult) {
console.log('Hawkins is safe for ' + finalResult + ' more seasons.');
}, failureCallback);
}, failureCallback);
}, failureCallback);
想象一下这种情况进一步发展,再有10到15个嵌套函数调用。是不是很可怕?
JavaScript 开发人员认识到这是一个问题,因此他们创建了Promises。
Promise 于 ES6(2015 年)中引入,它是一种在不中断事件循环的情况下格式化异步函数的替代方法。它返回一个特殊的 Promise 对象,该对象表示未来的结果。
有何不同?
其中很多都是格式化。
回调不会立即返回任何内容,它们将函数作为参数,然后告诉执行函数在异步任务完成时要做什么。
另一方面,Promise 会立即返回一个特殊的 Promise 对象。它们不需要函数参数,因此无需嵌套。您可以使用名为then()
的 Promise 方法指定异步任务完成时要执行的操作。
链接,又称友谊的力量
Promises真正令人惊叹的地方在于,当我们需要连续执行两个或多个异步操作时,可以使用它们的then()方法将它们链接起来。
每个链式 then() 函数都会返回一个新的承诺,与原始承诺不同,代表链中另一个异步步骤的完成。
您基本上可以将其理解为先做这个,然后做这个,然后做这个。
Promise 也有一个 catch() 方法。将 catch() 链接到链的末尾,将会返回链中任何失败的 Promise 的错误信息。设置在链中发生故障时要采取的操作也很有用。
Promise 链让我们摆脱令人讨厌的嵌套回调模式,并将我们的 JavaScript 代码扁平化为更易读的格式。
fightTheDemogorgon()
.then(function(result) {
return rollForDamage(result);
})
.then(function(seasonsLeft) {
return closeTheGateIn(seasonsLeft);
})
.then(function(finalResult) {
console.log('Hawkins is safe for ' + finalResult + ' more seasons.');
})
.catch(failureCallback);
使用 ES6 语法我们可以进一步简化它!
fightTheDemogorgon()
.then((result) => rollForDamage(result))
.then((seasonsLeft) => closeTheGateIn(seasonsLeft))
.then((finalResult) => console.log('Hawkins is safe for ' + finalResult + ' more seasons.'))
.catch(failureCallback);
击败野兽,逃离地狱
这里的野兽是异步调用,地狱是回调地狱。
没有什么可以阻止你以典型的回调方式嵌套 Promise 函数。但这没有必要!这通常是无意造成的,只是对 Promise 不熟悉。
你可以把 Promises 想象成披着新外衣的回调函数。它让异步代码看起来更简洁,提升了易用性和可读性,最重要的是,它让你摆脱了回调地狱。

ES8(2017)中引入了一种更新的方法,称为 Async/await。快来看看吧!
感谢阅读!
参考文献:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://www.youtube.com/watch?v=8aGhZQkoFbQ