ES6对外来控制流的承诺模式

时间:2015-03-03 22:07:20

标签: callback promise ecmascript-6 es6-promise

ES6承诺很棒。到目前为止,调整我的想法很容易 回调成语。我发现它自然会鼓励更多的模块化代码 当然错误处理更清晰。

但有几次我遇到的流量情况看起来并不像(?) 可以很容易地从nodebacks转换为promises(也许就是那样,但也许我只是对答案视而不见)。承诺是不可知的 关于下一个操作(或者如果有一个操作),使用起来似乎很难 使用API​​的承诺不只是采取回调,而且还返回

最常见的例子是“完成”回调。它显示在数据库连接之类的东西上,表示“返回到池的连接”,但我也看到它在很多其他地方出现。

function getSomeStupidConnection(cb) {
    var conn = /* ... */;
    var iNeedToBeToldWhenIAmDone = function() { /* ... */ };

    cb(conn, iNeedToBeToldWhenIAmDone);
}

getSomeStupidConnection(function(conn, done) {
    /* ... */

    conn.doLotsOfStuff(function(soMuchStuff) {

        /* stuff! so much fun! */

        /* okay conn go away I’m tired */

        done();
    });
});
像这样的流量反转显然不是你想要的API 首先,但它在那里,你有时不能真正避免它。同 回调,你可以将'call later'内部回调传递给原来的'外部' 打回来。它并没有完全导致关注的清晰分离,但在 至少它快速而简单。

是否存在适合此类情况的基于承诺的方法?一种说法, '这是解决价值 - 但是当链完成时,也会这样做'?一世 怀疑没有什么能与我刚才描述的完全匹配,因为它 不可能说连锁店已经“完成”,但也许我错过了一些 模式,让你接近,而不会弄得一团糟......


编辑:根据反馈到目前为止,我已经意识到根本没有办法将这样的API包装在真正的承诺中,因为你返回的承诺永远无法告诉你有关任何后续链式承诺的任何信息。它。但你可以伪造它。扭曲是结果相当脆弱;它必须假设需要连接对象的唯一then是紧随其后的那个。承诺的消费者需要了解它是一次性使用的连接,这是不明显的。因此,我并不是真的在实践中推荐它,但为了好奇,这里有一个隐藏done同时表现为(并最终成为)承诺链的解决方案:

/* jshint node: true, esnext: true */
'use strict';

// Assume this comes from an external library. It returns a connection and a
// callback to signal that you are finished with the connection.

function getConnectionExternal(cb) {
    let connection = 'Received connection.';
    let done = () => console.log('Done was called.');

    cb(null, connection, done);
}

// Our promisey wrapper for the above

function getConnection() {
    let _done;

    let promise = new Promise((resolve, reject) => {
        getConnectionExternal((err, connection, done) => {

            if (err) return reject(err);

            _done = (val) => {
                done();
                return val;
            };

            resolve(connection);
        });
    });

    let _then = promise.then.bind(promise);

    promise.then = (handler) => _then(handler).then(_done, _done);

    return promise;
}

// Test it out

getConnection()
    .then(connection => {
        console.log(connection);

        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('Finished using connection!');
                resolve('This should be after connection closes.');
            }, 200);
        });
    })
    .then(msg => console.log(msg))
    .catch(err => console.error(err));

控制台打印:

  • 收到连接。
  • 使用连接完成!
  • 完成了。
  • 这应该在连接关闭后。

4 个答案:

答案 0 :(得分:3)

阐述Bergi的解决方案,这被称为处置模式。它以多种形式存在于许多语言中 - 在Python中为with,在C#中为using,在Java中为try(){提供资源。有些语言本身通过像C#这样的析构函数来处理作用域中的资源。

一般的想法是将范围封装为值的生命周期。在您的情况下是数据库连接。它比在回调中调用done更加整洁,因为忘记调用done会更容易,这会导致开放连接和资源泄漏。同步它看起来像:

function scope(cb){
     try{ 
         var conn = getConnection(...);
         return cb(conn);
     } finally {
         conn.release();
     }
}

承诺版本并没有太大的不同:

function conn(data){
    var _connection;
    return getConnection().then(function(connection){
        _connection = connection; // keep a reference
        return data(_connection); // pass it to the function
    }).then(function(val){
         // release and forward
         _connection.release(); // if release is async - chain
         return val; 
    }, function(err){
         _connection.release();
         throw err; // forward error
    });
});

哪个会用:

conn(function(db){
    return db.query("SELECT * FROM ...");
}).then(function(result){ // handle result
    // connection is released here
});

答案 1 :(得分:2)

  

一种说法,'这是解决价值 - 但是当链完成时,也会这样做'?

不,本地承诺不提供这样的设施。我会使用一个带有promise-returns回调的资源函数,回调完成连接打开时需要完成的所有事情(在一个链中)。资源管理器函数不是将iNeedToBeTold传递给回调,而是观察承诺,并在结算时执行需要完成的任务。

function manageConnection(cb) {
    return getSomeConnection(…) // get the connections asynchronously - via a promise of course
    .then(function(conn) {
        function whenDone() {
            … // do what needs to be done
            return result;
        }
        var result = cb(conn);
        return result.then(whenDone, whenDone);
    });
}

manageConnection(function(conn) {
    return conn.doLotsOfStuff(soMuch)
    .then(function(stuff) {
        /* stuff! so much fun! */
    });
}).then(…)

答案 2 :(得分:2)

done()函数的问题是人们忘记调用它,导致泄漏。

我喜欢Bergi通过传入回调的答案,因为它很干净,但它不是很好,但是仍然是开放式的不安全的,例如如果人们链接到永远不会解决回调承诺的承诺,那么它就会停滞并泄漏。

这也是浏览器API中讨论的问题,我们正在思考的一种模式是返回

AutoClosingPromise

AutoClosingPromise就像一个承诺,但做了两件事:

  1. 它"关闭一张票" (调用完成)执行其.then()。

  2. 之后
  3. 此外,当它包含另一个承诺时,如果它看到从其.then()返回的另一个AutoClosingPromise,则它将其转发出去(将该承诺的票证 - 另一张票证传递给AutoClosingPromise)它从自己的.then()函数返回。

  4. 第一部分意味着API可以使用"票证返回AutoClosingPromise"保持资源打开(如开放计数)并确保在第一个.then()函数返回后票证将被关闭。

    第二部分允许调用者在即时.then()函数内对API进行额外的异步调用,只要票证在时间上重叠,就让API保持资源开放。

    这样做的一个特点是,资源不会在常规承诺中投标,只能在AutoClosing承诺中投放,从而避免泄漏风险。例如:

    var lock = new ExampleLock();
    lock.access("foo")
    .then(() => lock.set("foo1"))
    .then(() => lock.set("foo2"))
    .then(() => lock.set("foo3"))
    .then(() => {})
    .then(() => lock.set("foo4"))
    .catch(failed);
    

    会将资源(锁定)扩展到前三个,而不是第四个:

    setting foo1 [LOCKED]
    setting foo2 [LOCKED]
    setting foo3 [LOCKED]
    setting foo4 [UNLOCKED]
    

    以下是代码:

    function AutoClosingPromise(ticket, p) {
      this.pending = true;
      this.ticket = ticket;
    
      var close = result => {
        this.pending = false;
        if (this.ticket) {
          this.ticket.close();
          if (result && result.handoffTicket && this.returnedThenPromise) {
            // callback returned an AutoClosingPromise! Forward its ticket
            this.returnedThenPromise.takeTicket(result.handoffTicket());
          }
        }
        return result;
      };
      this.p = p.then(v => close(this.success && this.success(v)),
                      r => close(this.failure && this.failure(r)));
    }
    AutoClosingPromise.prototype = {
      then: function(success, failure) {
        if (this.pending && !this.success && !this.failure) {
          this.success = success;
          this.failure = failure;
          this.returnedThenPromise = new AutoClosingPromise(null, this.p);
          return this.returnedThenPromise;
        } else {
          return this.p.then(success, failure);
        }
      },
      takeTicket: function(ticket) { this.ticket = ticket; },
      handoffTicket: function() {
        var ticket = this.ticket;
        this.ticket = null;
        return ticket;
      }
    };
    

    和小提琴:http://jsfiddle.net/jib1/w0ufvahL(需要一个能理解es6箭头功能的浏览器,比如Firefox,例如不是Chrome)。

    由于API控制发出票证的所有异步调用,因此这应该是相当防漏的。例如。即使调用者完全忽略了从API返回的承诺,仍然会调用close。

    请注意,这是一个相当新的想法,而不是经过验证的构造,所以如果您最终使用它,请告诉我它是如何工作的。 ; - )

答案 3 :(得分:0)

我并不是100%肯定你会得到什么,但也许这就是你追求的目标?基本上嵌套的承诺......

let connection = function(){
  return new Promise(function(resolve, reject){
    window.setTimeout(resolve("There"), 5000);
  })
}

let connectionManager = function(){
  return connection().then(function(value){
    console.log("Hello");

    return value;
  });
}

connectionManager().then(function(value){
  console.log(value);
});

Here it is on Babel REPL

如果您尝试使用promises包装现有的异步功能,这里的示例也可能有所帮助:http://www.2ality.com/2014/10/es6-promises-api.html#example%3A_promisifying_xmlhttprequest

根据您需要的嵌套要求,您可能希望使用promise对象解析promise;如果您需要澄清,请随时发表评论:)