不是承诺只是回调

Aren't promises just callbacks?

本文关键字:回调 承诺      更新时间:2023-09-26

我已经开发JavaScript好几年了,我完全不理解关于承诺的大惊小怪。

似乎我所做的就是改变:

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表示异步操作的未来结果。当然,按照你的方式来写,你得到的好处很少。但是,如果您按照它们的使用方式编写它们,则可以以类似于同步代码的方式编写异步代码,并且更易于遵循:

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

当然,代码不多,但可读性强多了。

但这不是结束。让我们来看看真正的好处:如果您想检查任何步骤中的任何错误,该怎么办?对于回调来说,这将是地狱,但是对于承诺来说,这是小菜一碟:

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调用api, api2, api3可以同时运行(例如,如果它们是AJAX调用),但您需要等待三个?如果没有承诺,就必须创建某种类型的计数器。对于承诺,使用ES6符号,是另一个小菜一碟,非常整洁:

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是异步回调。它们不能做回调不能做的任何事情,并且你面对的异步问题和普通回调是一样的。

然而,promise 比回调更。它们是一个非常强大的抽象,允许更干净、更好的功能代码和更少容易出错的样板。

那么主要思想是什么?

promise是表示单个(异步)计算结果的对象。它们只解析一次结果。这有几点含义:

承诺实现一个观察者模式:

  • 在任务完成之前,您不需要知道将使用该值的回调。
  • 不期望回调函数作为函数的参数,你可以很容易地return一个Promise对象
  • 承诺将存储值,你可以透明地添加回调,只要你想。它将在结果可用时被调用。"透明"意味着当你有一个承诺并向它添加一个回调时,无论结果是否已经到达,它都不会对你的代码产生影响——API和契约是相同的,大大简化了缓存/记忆。
  • 您可以轻松地添加多个回调

承诺是可链的(一元的,如果你愿意的话):

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

听起来复杂吗?下面是一个代码示例。

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抽象允许更好的函数可组合性。例如,then函数用于链接,all函数为多个并行等待promise的合并结果创建一个promise。

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

更不用说把事情变成承诺了。

对于好的承诺库来说,这是相当微不足道的,参见如何将现有的回调API转换为承诺?

除了已经确定的答案之外,使用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))

最后,如果你想达到超大质量黑洞的水平,承诺可以等待:

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

除了上面的精彩答案,还可以再加2分:

1。语义的差异:

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

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

2。控制反转

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

请参考Javascript事件循环获取解释。

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

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

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

承诺不是回调,两者都是便于异步编程的编程习惯。使用使用协程或生成器返回承诺的异步/等待风格的编程可以被认为是第三种这样的习惯用法。这些习惯用法在不同编程语言(包括Javascript)中的比较如下:https://github.com/KjellSchubert/promise-future-task

不,一点也不

回调函数是JavaScript中简单的函数,它被调用,然后在另一个函数执行完成后执行。那么它是如何发生的呢?

实际上,在JavaScript中,函数本身被认为是对象,因此和所有其他对象一样,甚至函数也可以作为参数发送给其他函数。最常见和最通用的用例是JavaScript中的setTimeout()函数。

与回调相比,

承诺只不过是处理和构造异步代码的一种更临时的方法。

Promise在构造函数中接收两个回调函数:resolve和reject。承诺内部的这些回调为我们提供了对错误处理和成功案例的细粒度控制。当promise执行成功时使用resolve回调,而reject回调用于处理错误情况。

没有承诺只是回调的包装

的例子你可以使用javascript原生承诺节点js

my cloud 9 code link : https://ide.c9.io/adx2803/native-promises-in-node
/**
* Created by dixit-lab on 20/6/16.
*/
var express = require('express');
var request = require('request');   //Simplified HTTP request client.

var app = express();
function promisify(url) {
    return new Promise(function (resolve, reject) {
    request.get(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        resolve(body);
    }
    else {
        reject(error);
    }
    })
    });
}
//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
//get the post with post id 100
promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
var obj = JSON.parse(result);
return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
})
.catch(function (e) {
    console.log(e);
})
.then(function (result) {
    res.end(result);
}
)
})

var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("Example app listening at http://%s:%s", host, port)
})

//run webservice on browser : http://localhost:8081/listAlbums

承诺概述:

在JS中,我们可以将异步操作(例如数据库调用,AJAX调用)包装在promise中。通常我们希望在检索到的数据上运行一些额外的逻辑。JS承诺有处理函数来处理异步操作的结果。处理程序函数甚至可以在其中包含其他异步操作,这些操作可以依赖于前一个异步操作的值。

承诺总是有以下三种状态之一:

  1. pending:每个承诺的开始状态,既没有完成也没有拒绝。
  2. completed:操作成功完成。
  3. rejected: The operation failed.

一个挂起的承诺可以用一个值来解决/完成或拒绝。然后调用以下以回调函数为参数的处理程序方法:

  1. Promise.prototype.then():当promise被解析时,这个函数的callback参数将被调用。
  2. Promise.prototype.catch():当promise被拒绝时,这个函数的callback参数将被调用。

虽然上述方法可以获得回调参数,但它们比使用只有回调在这里是一个例子,将澄清很多:

function createProm(resolveVal, rejectVal) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                console.log("Resolved");
                resolve(resolveVal);
            } else {
                console.log("Rejected");
                reject(rejectVal);
            }
        }, 1000);
    });
}
createProm(1, 2)
    .then((resVal) => {
        console.log(resVal);
        return resVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
        return resVal + 2;
    })
    .catch((rejectVal) => {
        console.log(rejectVal);
        return rejectVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
    })
    .finally(() => {
        console.log("Promise done");
    });

  • createProm函数创建一个承诺,该承诺在1秒后基于随机Nr被解析或拒绝
  • 如果promise被解析,则调用第一个then方法,并将解析的值作为回调的参数传入
  • 如果promise被拒绝,则调用第一个catch方法并将被拒绝的值作为参数传入
  • catchthen方法返回承诺,这就是为什么我们可以链接它们。它们在Promise.resolve中包装任何返回值,在Promise.reject中包装任何抛出值(使用throw关键字)。因此,任何返回的值都被转换成一个承诺,在这个承诺上,我们可以再次调用handler函数。
  • 承诺链给我们提供了比嵌套回调更精细的控制和更好的概述。例如,catch方法处理在catch处理程序之前发生的所有错误。

Promises允许程序员编写比使用回调更简单、更易读的代码。


在程序中,我们希望按顺序执行一些步骤。

function f() {
   step_a();
   step_b();
   step_c();
   ...
}

通常每一步之间都有信息传递。

function f( x ) {
   const a = step_a( x );
   const b = step_b( a );
   const c = step_c( b );
   ...
}

其中一些步骤可能需要(相对)较长的时间,所以有时您希望与其他事情并行执行它们。一种方法是使用线程。另一个是异步编程。(这两种方法各有利弊,这里不再讨论。)这里,我们讨论的是异步编程。

在使用异步编程时,实现上述目标的简单方法是提供一个回调,一旦步骤完成就会调用。

// Each of step_* calls the provided function with its return value once complete.
function f() {
   step_a( x,
      function( a ) {
         step_b( a,
            function( b ) {
               step_c( b,
                  ...
               );
            },
         );
      },
   );
}

这很难读。承诺提供了一种平面化代码的方法。

// Each of step_* returns a promise.
function f( x ) {
   step_a( x )
   .then( step_b )
   .then( step_c )
   ...
}

返回的对象被称为promise,因为它代表了函数的未来结果(即承诺的结果)(可以是一个值或一个异常)。

尽管承诺很有帮助,但使用承诺仍然有点复杂。这就是asyncawait发挥作用的地方。在声明为async的函数中,可以使用await代替then

// Each of step_* returns a promise.
async function f( x )
   const a = await step_a( x );
   const b = await step_b( a );
   const c = await step_c( b );
   ...
}

不可否认,这比使用回调更具可读性。

JavaScript Promise实际上使用回调函数来确定在Promise被解析或拒绝后要做什么,因此两者并没有本质上的不同。Promises背后的主要思想是接受回调——尤其是当你想要执行某种操作时的嵌套回调,但它会更具有可读性。