Promise不就是回调吗?

IT技术 javascript callback promise q bluebird
2020-12-25 16:27:33

我已经开发 JavaScript 几年了,我根本不明白关于 promise 的大惊小怪。

似乎我所做的就是改变:

api(function(result){
    api2(function(result2){
        api3(function(result3){
             // do work
        });
    });
});

无论如何,我可以使用像async这样的库,例如:

api().then(function(result){
     api2().then(function(result2){
          api3().then(function(result3){
               // do work
          });
     });
});

这是更多的代码和更少的可读性。我在这里没有得到任何东西,它也不会突然神奇地“平坦”。更不用说必须将事情转换为Promise。

那么,这里的 Promise 有什么大惊小怪的呢?

6个回答

Promise不是回调。Promise代表异步操作未来结果当然,按照您的方式编写它们,您几乎没有什么好处。但是,如果您按照它们的使用方式编写它们,则可以以类似于同步代码的方式编写异步代码,并且更易于遵循:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
});

当然,不是更少的代码,而是更具可读性。

但这还不是结束。让我们发现真正的好处:如果您想检查任何步骤中的任何错误怎么办?用回调来做这件事会很糟糕,但用Promise,是小菜一碟:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
});

try { ... } catch几乎相同

更好的是:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
}).then(function() {
     //do something whether there was an error or not
     //like hiding an spinner if you were performing an AJAX request.
});

更妙的是:如果这些3调用什么apiapi2api3可以同时运行(例如,如果他们是AJAX调用),但你需要等待三个?没有Promise,你应该创建某种计数器。使用 ES6 表示法的 promise 是另一块蛋糕,而且非常整洁:

Promise.all([api(), api2(), api3()]).then(function(result) {
    //do work. result is an array contains the values of the three fulfilled promises.
}).catch(function(error) {
    //handle the error. At least one of the promises rejected.
});

希望您现在以全新的眼光看待 Promise。

@Pacerier 因为 Future 没有被 jQuery 污染?
2021-02-14 16:27:33
使用 Promise 没有缺点吗?如果是这样,我应该尽可能使用Promise而不是回调吗?
2021-02-22 16:27:33
他们真的不应该把它命名为“Promise”。“未来”至少好 100 倍。
2021-02-23 16:27:33
如果有异步的operatingsapi2api3.then只有在这些异步操作完成后才调用最后一个吗?
2021-02-23 16:27:33
替代模式(取决于所需的内容:api().then(api2).then(api3).then(doWork); 也就是说,如果 api2/api3 函数从最后一步获取输入,并自己返回新的Promise,它们可以在没有额外包装的情况下链接起来。也就是说,它们组合在一起。
2021-03-02 16:27:33

是的,Promise 是异步回调。它们不能做回调不能做的任何事情,并且您面临与普通回调相同的异步问题。

然而,Promise是不仅仅是回调。它们是一个非常强大的抽象,允许更清晰、更好的功能性代码和更少出错的样板。

那么主要思想是什么?

Promise 是表示单个(异步)计算结果的对象。他们对那个结果下定决心一次。这有几个意思:

Promise 实现了一个观察者模式:

  • 在任务完成之前,您不需要知道将使用该值的回调。
  • 您可以轻松地return使用 Promise 对象,而不是期望回调作为函数的参数
  • Promise将存储该值,您可以随时透明地添加回调。当结果可用时将调用它。“透明”意味着当你有一个Promise并向它添加一个回调时,结果是否已经到达对你的代码没有影响——API 和合约是相同的,大大简化了缓存/记忆。
  • 您可以轻松添加多个回调

Promise 是可链接的monadic如果你愿意的话):

  • 如果您需要转换Promise所代表的值,您可以在Promise上映射一个转换函数,并返回一个代表转换结果的新Promise。您无法以某种方式同步获取值以使用它,但是您可以轻松地在 Promise 上下文中提升转换。没有样板回调。
  • 如果要链接两个异步任务,可以使用该.then()方法。它将使用第一个结果调用回调,并为回调返回的Promise的结果返回一个Promise。

听起来很复杂?是时候写一个代码示例了。

var p1 = api1(); // returning a promise
var p3 = p1.then(function(api1Result) {
    var p2 = api2(); // returning a promise
    return p2; // The result of p2 …
}); // … becomes the result of p3

// So it does not make a difference whether you write
api1().then(function(api1Result) {
    return api2().then(console.log)
})
// or the flattened version
api1().then(function(api1Result) {
    return api2();
}).then(console.log)

扁平化不会神奇地出现,但您可以轻松做到。对于重度嵌套的示例,(接近)等效项将是

api1().then(api2).then(api3).then(/* do-work-callback */);

如果看到这些方法的代码有助于理解,这里有几行最基本的promise lib

Promise有什么大惊小怪的?

Promise 抽象允许更好的函数可组合性。例如,在thenfor chaining旁边,该all函数为多个并行等待的 promise 的组合结果创建一个 promise。

最后但并非最不重要的 Promise 带有集成的错误处理。计算的结果可能是,要么Promise被兑现了value,或者它拒绝与一个道理。所有的组合函数都会自动处理这个并在Promise链中传播错误,所以你不需要在任何地方显式地关心它 - 与普通的回调实现相反。最后,您可以为所有发生的异常添加专用的错误回调。

更不用说必须将事情转换为Promise。

对于好的 Promise 库,这实际上非常简单,请参阅如何将现有的回调 API 转换为 Promise?

嗨,Bergi,你有什么有趣的东西要添加到这个 SO 问题中吗?stackoverflow.com/questions/22724883/...
2021-02-08 16:27:33
请注意:您不能使用.then(console.log),因为 console.log 取决于控制台上下文。这样就会导致非法调用错误。使用console.log.bind(console)x => console.log(x)绑定上下文。
2021-02-28 16:27:33
@hege_hegedus:有些环境console已经绑定了方法。当然,我只是说两个嵌套具有完全相同的行为,而不是它们中的任何一个都可以工作:-P
2021-03-05 16:27:33
那很棒。这就是我需要的:更少的代码和更多的解释。谢谢你。
2021-03-06 16:27:33
@Sebastien:我对 Scala 了解不多(目前),我只能重复本杰明所说的:-)
2021-03-08 16:27:33

除了已经确定的答案之外,使用 ES6 箭头函数,Promises 从一个不起眼的小蓝矮星直接变成了一个红巨星。那即将坍缩成超新星:

api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))

正如oligofren指出的那样,在 api 调用之间没有参数,您根本不需要匿名包装函数:

api().then(api2).then(api3).then(r3 => console.log(r3))

最后,如果你想达到超大质量黑洞级别,可以等待 Promises:

async function callApis() {
    let api1Result = await api();
    let api2Result = await api2(api1Result);
    let api3Result = await api3(api2Result);

    return api3Result;
}
我知道你的意思是诗意的,但Promise与“超新星”相去甚远。想到了违反一元法则或缺乏对更强大的用例(例如取消或返回多个值)的支持。
2021-02-07 16:27:33
这让 Promises 听起来像是一场宇宙灾难,我认为这不是你的本意。
2021-02-09 16:27:33
@MichaelMcGinnis——Promises 对沉闷的回调地狱的有益影响就像太空黑暗角落中爆炸的超新星。
2021-02-09 16:27:33
“使用 ES6 箭头函数,Promises 从一颗不起眼的小蓝星直接变成了一颗红巨星。那将要坍塌成超新星”翻译:将 ES6 箭头函数与 Promises 结合起来很棒:)
2021-02-25 16:27:33
如果您没有在apiX方法中使用参数,您不妨完全跳过箭头函数:api().then(api2).then(api3).then(r3 => console.log(r3))
2021-03-08 16:27:33

除了上面很棒的答案之外,还可以添加 2 点:

1.语义差异:

Promise 可能在创建时就已经解决了。这意味着它们保证条件而不是事件如果它们已经被解析,则传递给它的解析函数仍会被调用。

相反,回调处理事件。因此,如果您感兴趣的事件在注册回调之前发生,则不会调用回调。

2. 控制反转

回调涉及控制反转。当您使用任何 API 注册回调函数时,Javascript 运行时会存储回调函数并在它准备好运行后从事件循环中调用它。

有关解释,请参阅Javascript 事件循环

使用Promises,控制驻留在调用程序中。如果我们存储 promise 对象,则 .then() 方法可以随时调用

很好,这个 ->“有了 Promises,控制权在调用程序中。如果我们存储了 Promise 对象,那么 .then() 方法可以在任何时候被调用。”
2021-02-19 16:27:33
我不知道为什么,但这似乎是一个更好的答案。
2021-03-05 16:27:33

除了其他答案之外,ES2015 语法与 promise 无缝融合,减少了更多样板代码:

// Sequentially:
api1()
  .then(r1 => api2(r1))
  .then(r2 => api3(r2))
  .then(r3 => {
      // Done
  });

// Parallel:
Promise.all([
    api1(),
    api2(),
    api3()
]).then(([r1, r2, r3]) => {
    // Done
});