我从事JavaScript的开发已经有几年了,我完全不了解关于promise的事情。

我所做的似乎就是改变:

br />
无论如何我都可以使用像async这样的库,它具有类似以下内容:

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


哪个代码更多,可读性更差。我在这里什么都没得到,也不是突然变得神奇地“平坦”。更不用说必须将事情转换为承诺了。

那么,关于承诺的大惊小怪是什么?

评论

主题:关于Html5Rocks上的Promises的内容非常丰富:html5rocks.com/en/tutorials/es6/promises

Fyi,您接受的答案是琐碎的好处的老清单,这些清单根本不是诺言的重点,甚至都不说服我使用诺言:/。正如奥斯卡的回答所述,说服我使用诺言的是DSL方面

@Esailija很好,你的leet说服了我。我接受了另一个答案,尽管我认为Bergi的答案也提出了一些非常好的(和不同的)观点。

@Esailija“说服我遵守诺言的是奥斯卡回答中所描述的DSL方面” <<什么是“ DSL”?您指的是“ DSL方面”?

@monsto:DSL:域特定语言,一种专门设计用于系统的特定子集的语言(例如SQL或ORM与数据库对话,正则表达式以查找模式等)。在这种情况下,“ DSL”是Promise的API,如果您按照Oscar的方式来构造代码,则几乎就像语法糖,它补充了JavaScript以解决异步操作的特定上下文。诺言创造了一些习语,使它们变成几乎一种旨在使程序员更容易掌握这种结构的难以捉摸的思维的语言。

#1 楼

承诺不是回调。一个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.
});

甚至更好:如果那三个对apiapi2api3的调用可以同时运行(例如,如果它们是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”。 “未来”至少要好100倍。

–起搏器
2014年5月2日在18:47



@Pacerier,因为Future没有被jQuery污染吗?

– Esailija
15年1月31日,12:01

备用模式(取决于所需的内容:api()。then(api2).then(api3).then(doWork);即,如果api2 / api3函数从最后一步获取输入,并自己返回新的Promise,则它们可以直接链接而无需额外包装,也就是说,它们组成了。

– Dtipson
15/12/22在19:11



如果api2和api3中存在异步操作怎么办?仅在这些异步操作完成后才调用最后一个.then吗?

– NiCk Newman
16 Jan 24 '19:45



你为什么给我加标签?我只是修正了一点语法。我不是JS专家。 :)

–斯科特·阿西塞夫斯基(Scott Arciszewski)
16年4月29日在15:37

#2 楼

是的,Promise是异步回调。它们无法做回调不能做的任何事情,并且异步处理和普通回调都面临同样的问题。

但是,承诺不仅仅是回调。它们是非常强大的抽象,它允许更简洁,更好的功能代码,并且不易出错。


那么主要思想是什么?


Promise是表示单个(异步)计算结果的对象。他们只解决一次该结果。这意味着几件事:

Promise实现观察者模式:


您不需要知道在值之前使用该值的回调任务完成。
您可以轻松地return一个Promise对象,而不是期望将回调作为函数的参数,而Promise将存储该值,并且可以在需要时透明地添加回调。结果可用时将调用它。 “透明度”表示当您有一个诺言并向其添加回调时,结果是否到来对您的代码没有影响-API和协定相同,从而大大简化了缓存/存储。 br />您可以轻松地添加多个回调

可链接的承诺(如果需要,可以是monadic):


如果您需要转换承诺的价值表示,您可以在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抽象允许更好的功能可组合性。例如,在链接的then旁边,all函数为多个并行等待的Promise的组合结果创建一个Promise。

最后但并非最不重要的是,Promises带有集成的错误处理。计算的结果可能是允诺通过一个值实现,或者因某种原因而被拒绝。与纯回调实现相反,所有组合函数都会自动处理此问题并在Promise链中传播错误,因此您无需在任何地方显式地关心它。最后,您可以为所有发生的异常添加专用的错误回调。


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


实际上,对于良好的Promise库来说,这是微不足道的,请参阅如何将现有的回调API转换为Promise?

评论


嗨,Bergi,您是否有任何有趣的事情要添加到SO问题中? stackoverflow.com/questions/22724883/…

–塞巴斯蒂安·洛伯(Sebastien Lorber)
2014年4月1日14:38在

@Sebastien:我对Scala还不太了解,但我只能重复本杰明所说的:-)

–贝尔吉
2014年4月1日14:57



请注意:不能使用.then(console.log),因为console.log取决于控制台上下文。这样会导致非法调用错误。使用console.log.bind(console)或x => console.log(x)绑定上下文。

– Tamas Hegedus
15年11月20日在11:07

@hege_hegedus:在某些环境中已经绑定了控制台方法。当然,我只说过两个嵌套都有完全相同的行为,而不是它们中的任何一个都可以工作:-P

–贝尔吉
15年11月20日在11:46

那很棒。这就是我需要的:更少的代码和更多的解释。谢谢。

–亚当·帕特森(Adam Patterson)
17 Mar 5 '17 at 20:58

#3 楼

除了已经确定的答案之外,借助ES6箭头功能,Promise还从适度发光的小蓝矮人转变为红巨人。即将崩溃成超新星:

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;
}


评论


“具有ES6箭头功能,Promises从适度发光的小蓝星直接变成红色巨人。这将崩溃成超新星”转换:将ES6箭头功能与Promises结合起来真是棒极了:)

–user3344977
17年6月15日在21:18

这使Promises听起来像一场宇宙灾难,我认为这不是你的意图。

– Michael McGinnis
17-10-31在8:13

如果您未在apiX方法中使用参数,则最好完全跳过箭头函数:api()。then(api2).then(api3).then(r3 => console.log(r3))。

–寡核苷酸
17年7月7日在18:55

@MichaelMcGinnis-Promises对沉闷的回调地狱的有益影响就像是太空暗角中爆炸的超新星。

–约翰·魏兹
18年5月24日在8:19

我知道你是诗意的,但是诺言与“超新星”相去甚远。我想到了违反单子法或缺乏对更强大的用例(例如取消或返回多个值)的支持。

–德米特里·扎伊采夫(Dmitri Zaitsev)
19年6月9日在4:26

#4 楼

除了上述令人敬畏的答案外,还可以添加2点:

1。语义差异:

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

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

2。控制反转

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

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

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

评论


我不知道为什么,但这似乎是一个更好的答案。

–radiantshaw
19年2月26日在5:40

#5 楼

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

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

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


#6 楼

不,一点也不。

回调只是JavaScript中的函数,要在另一个函数的执行完成后调用它们,然后再执行。那怎么发生的呢?

实际上,在JavaScript中,函数本身被视为对象,因此,与所有其他对象一样,甚至函数也可以作为参数发送给其他函数。人们可以想到的最常见,最通用的用例是JavaScript中的setTimeout()函数。与通过回调进行处理相比,承诺只是处理和构造异步代码的一种更为简易的方法。 。

Promise在构造函数中收到两个回调:解析和拒绝。 promise中的这些回调为我们提供了对错误处理和成功案例的细粒度控制。成功执行promise时将使用resolve回调,并使用reject回调来处理错误情况。

#7 楼

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

#8 楼

没有承诺只是包装在回调上

示例
您可以将Javascript本机promise与节点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


#9 楼

JavaScript Promises实际上使用回调函数来确定在Promise被解决或拒绝后该怎么做,因此两者在本质上没有什么不同。 Promises背后的主要思想是采用回调-尤其是嵌套的回调,您想在其中执行某种操作,但它更具可读性。

#10 楼

承诺概述:

在JS中,我们可以将异步操作(例如数据库调用,AJAX调用)包装在promise中。通常,我们要对检索到的数据运行一些其他逻辑。 JS Promise具有处理程序功能,用于处理异步操作的结果。处理程序函数中甚至可以包含其他异步操作,这些操作可能依赖于先前异步操作的值。

promise始终具有以下3种状态:


待处理:每个诺言的开始状态,既未实现也不被拒绝。
已实现:操作成功完成。
被拒绝:操作失败。

可以待定的诺言已解决/已满或被拒绝的值。然后调用以下将回调作为参数的处理程序方法:



Promise.prototype.then():当promise被解决时,将调用此函数的回调参数。

Promise.prototype.catch():当promise被拒绝时,该函数的回调参数将被调用。

尽管上述方法获得了回调参数,但它们比仅使用回调方法优越得多。是一个可以阐明很多内容的示例:

示例




 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被解析或拒绝。
如果承诺被解析,则调用第一个then方法并将解析后的值作为回调的参数传递给我们
如果诺言被拒绝,则会调用第一个catch方法,并将拒绝的值作为参数传递给我们
catchthen方法返回诺言,这就是我们可以链接它们的原因。他们在Promise.resolve中包装所有返回值,并在throw中包装任何抛出的值(使用Promise.reject关键字)。因此,任何返回的值都将转换为一个Promise,并且在此Promise上,我们可以再次调用处理程序函数。
Promise链为我们提供了比嵌套回调更精细的控制和更好的概览。例如,catch方法处理所有在catch处理程序之前发生的错误。