【问题标题】:What does it mean for promises to be immutable and their guaranteed value?承诺是不可变的及其保证值意味着什么?
【发布时间】:2017-07-24 23:30:35
【问题描述】:

我正在尝试理解differences between es6 promises and regular callbacks,但没有得到以下示例。有人可以展示使用回调执行以下操作会是什么样子吗?

// an immediately resolved promise
var p2 = Promise.resolve("foo"); 

// can get it after the fact, unlike events
p2.then((res) => console.log(res)); 

var p = new Promise(function(resolve, reject) {  
   setTimeout(() => resolve(4), 2000);
});

// handler can't change promise, just value
p.then((res) => {  
  res += 2;  
  console.log(res);
});

// still gets 4
p.then((res) => console.log(res));

【问题讨论】:

  • 承诺是一种单向闩锁。一种是用价值解决或用理由拒绝,它的状态和价值/理由永远不会改变。所以,无论你对同一个承诺执行多少次.then(),你总是会得到相同的结果。普通回调只是回调,每次调用它们时都可以赋予不同的值。如果您想在某些操作完成时只收到一次通知,请使用 Promise。如果您想多次收到通知,请使用普通回调或侦听器或观察者或其他可以多次触发的机制。

标签: javascript callback ecmascript-6 es6-promise


【解决方案1】:

promise 是一个单向闩锁。一旦它被一个值解决或被一个原因拒绝,它的状态和值/原因就永远不会改变。所以,无论你对同一个承诺执行多少次.then(),你总是会得到相同的结果。这就是“不可变”的意思。

我不确定您所说的保证值是什么意思。不能保证承诺会永远解决。它可能会拒绝(因此没有值),或者如果操作永远不会完成,它可能永远不会解决或拒绝。

Promise 设计的操作类型的一个示例是异步操作,例如 Ajax 调用或从文件中读取一些字节。该操作是异步的(解释器的正常执行在操作开始后继续)并且操作具有特定的开始和结束。在大多数情况下,操作可能成功完成,在这种情况下它可以有一个值,或者它可能以错误结束,在这种情况下它有一个错误。 value 和 error 都可以是对象,因此如果结果不仅仅是一个简单的值,它们可以有许多属性。

例如,Ajax 调用具有特定的开始和结束。它不能多次结束,因此它是 promise 的完美匹配。您会得到一个表示 ajax 操作最终结果的承诺。然后注册一个完成处理程序和一个拒绝处理程序,当操作完成时将调用其中一个或另一个。

普通回调只是回调,每次调用时都可以赋予不同的值,并且可以多次调用。

如果您希望在某个操作完成并且该操作具有特定的开始和结束时仅收到一次通知,请使用承诺。

如果您想获得多次通知,请使用普通回调或事件侦听器或观察者或其他可以多次触发的机制。


举个简单的例子,setTimeout() 与 Promise 配合得非常好。

function delay(t) {
    return new Promise((resolve, reject) => {
        resolve();
    }, t);
}

// delay 100ms before starting the operation
delay(100).then(run);

或者,使用 Bluebird Promise 库进行更复杂的操作,以循环浏览 URL 列表、下载内容、解析内容、在内容中查找某些特定 URL,然后将它们全部收集(也称为抓取) :

const Promise = require('bluebird');
const request = Promise.promisifyAll(require('request'), {multiArgs: true});
const cheerio = require('cheerio');

function getAsync() {
    return request.getAsync.apply(request, arguments).then(argArray => {
        // return only the response html
        if (argArray[0].statusCode !== 200) {
            throw new Error("response statusCode = " + argArray[0].statusCode);
        }
        return argArray[1];
    });
}

const urls = [....];
Promise.mapSeries(urls, url => {
     return getAsync({url: url, gzip: true}).then(html => {
         let $ = cheerio.load(html);
         let resources = $("#external_resources_list li a.filename");
         resources.each(index, link) => {
             let href = $(link).attr("href");
             console.log(href);
             results.push(href);
         });
     }).catch(err => {
         // log error, but keep going
         console.log(url, err);
     });
}).then(() => {
    // got all results here
    console.log(results);
});

而且,setInterval() 根本不适用于 Promise,因为它想在每次经过时间间隔时重复通知您,而这根本不适用于 Promise。坚持回调setInterval()

【讨论】:

  • 为什么一个promise只让你得到一次通知?不是每次发出 AJAX 请求时都会创建一个新的 Promise 吗?因此,每个 Promise 都对应于每个 AJAX 请求,那么这就是您会收到多次通知的方式,对吧?
  • 保证,我的意思是链接文章的作者我说过“我们保证接收该值,无论我们何时为它注册处理程序,即使它已经解决(相反到可能导致竞争条件的事件)。”
  • @stackjlei - 是的,如果你创建一个新的 Promise 并注册一个新的 .then() 处理程序,那么新的 Promise 中将会有一个状态变化的新通知。我的观点是,一个给定的承诺只有一个状态变化。之后,它不能改变状态,因此不能被重新用于新的异步操作。
  • @stackjlei - 是的,如果一个 promise 被解析为一个值并且你安装了一个 .then() 处理程序,你保证得到你的 .then() 处理程序被解析的值调用,无论 promise 是在安装 .then() 处理程序之前或之后解决。这是 Promise 的一个有用特性。如果在您安装 .then() 处理程序时承诺已经解决,那么您的 .then() 处理程序将在事件循环的下一个滴答声中被调用。如果在您安装了.then() 处理程序后承诺已解决,那么您的处理程序将在它解决后的下一个滴答声中被调用。
【解决方案2】:

为了与标准回调系统进行比较,让我们创建一个可以生成此类通知对象的类。它将有一个很像Promise 的接口,但它实现了一个简单的回调系统:

class Notifier {
    constructor(executor) {
        // The object will maintain a list of callbacks
        this.callbacks = [];
        // The executor is executed now and will allow the client
        // to determine the logic of this notifier object:
        // ...when the callbacks need to be called and with which value:
        executor( (res) => {
            // The client can call this resolve function to indicate
            // the value. So now the callbacks need to be called with it:
            this.callbacks.forEach(callback => callback(res));
        });
    }
    addListener(callback) {
        // This method resembles the `then` of promises: it allows
        // a client to pass a callback function, that should be called
        // when the value becomes available (i.e. when the event triggers).
        this.callbacks.push(callback);
    }
}; 

因此,与Promise 一样,您可以向此类的构造函数传递一个函数来做一些工作并在适当的时候指示值。您还可以将侦听器附加到它,它将在值可用时调用。

最后这句话突出了一个重要事实:如果值可用,但您尚未附加侦听器(回调),即使您在事实之后附加侦听器,您也会错过通知。

这是基于回调的代码,您可以将其与文章中引用的代码进行比较:

class Notifier {
    constructor(executor) {
        // The object will maintain a list of callbacks
        this.callbacks = [];
        // The executor is executed now and will allow the client
        // to determine the logic of this notifier object:
        // ...when the callbacks need to be called and with which value:
        executor( (res) => {
            // The client can call this resolve function to indicate
            // the value. So now the callbacks need to be called with it:
            this.callbacks.forEach(callback => callback(res));
        });
    }
    addListener(callback) {
        // This method resembles the `then` of promises: it allows
        // a client to pass a callback function, that should be called
        // when the value becomes available (i.e. when the event triggers).
        this.callbacks.push(callback);
    }
}; 

// a notifier that immediately notifies the result
f2 = new Notifier( (resolve) => resolve("foo") );
// but since no-one was listening, no callback is called.

// canNOT get it after the fact, unlike promises
f2.addListener((res) => console.log(res));
// ... nothing gets called or printed: we are too late.

// 
var f = new Notifier(function(resolve) {  
   setTimeout(() => resolve({ data: 4}), 2000);
});

// handler CAN change the outcome
f.addListener((res) => {  
  res.data += 2;
  console.log(res.data);
});

// ... now also this one gets 6!
f.addListener((res) => console.log(res.data));

【讨论】:

  • 您何时希望使用立即通知结果的通知程序?我认为承诺的全部意义在于异步?
  • Promise 对于调用 then 回调确实是异步的。通知者通常会在不同的时间点调用回调,每次都有不同的目的和值。如果通知程序在创建时立即调用回调函数,这确实不是很有用。但这旨在将最简单的回调实现(不必保证异步调用)与您引用的文章中介绍的 Promise 实现进行比较。
  • 当然,如果你从这样一个通知对象开始实现承诺的所有特性(和限制),那么你可以让它像一个承诺一样行事。请参阅an other answer of mine,我在其中“从头开始”提供了实现承诺概念的代码。
【解决方案3】:

当 promise 变量被解析时,它被调用时解析的值返回。 要使用多个,您必须按如下方式调用它。

var p = new Promise(function(resolve, reject) {  
   setTimeout(() => resolve(4), 2000);
});

p.then((res) => {  
  res += 2;  
  console.log(res);
  return res
})
.then((res) => console.log(res));

【讨论】:

  • 这并不能解释回调和承诺之间的区别......
猜你喜欢
  • 2019-02-16
  • 1970-01-01
  • 2020-06-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-12
  • 2012-10-08
  • 1970-01-01
  • 2023-03-10
相关资源
最近更新 更多