用于奇异控制流的 ES6 Promise 模式
ES6 Promise patterns for exotic control flows
ES6 Promise 很棒。到目前为止,很容易调整我的想法
回调成语。我发现它自然地鼓励更多的模块化代码,并且
当然错误处理更清晰。
但有几次我遇到过看起来(?)不像他们那样的心流情况
可以很容易地从 nodebacks 转换为 promises(也许就是这样,但也许我只是对答案视而不见)。因为承诺是不可知的
关于下一个操作(或者如果有的话),似乎很难使用
API 的承诺不仅接受回调,而且 return 它们。
想到的最常见的例子是“完成”回调。它出现在数据库连接之类的东西中,表示“return 连接到池”,但我也看到它出现在很多其他地方。
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 中拥有的东西
首先,但它就在那里,有时你无法真正避免它。和
回调,您可以将“稍后调用”内部回调传递给原始的“外部”
打回来。它并不能完全将关注点分开,但在
至少它又快又简单。
是否有适合这种情况的基于 Promise 的方法?一种说法,
“这是 resolve 值——但是当链条完成时,也这样做”?我
怀疑没有什么能完全符合我刚才描述的,因为它
真的不可能说链条已经“完成”,但也许我遗漏了一些
让您接近它而又不会弄得一团糟的模式...
编辑:根据目前的反馈,我意识到根本没有办法用真正的承诺来包装这样的 API,因为你 return 永远无法说出的承诺你对任何后续的链式承诺都有任何了解。但是你可以伪造它。扭曲的是结果相当脆弱。它必须假定唯一需要连接对象的 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));
控制台打印:
- 收到连接。
- 使用连接完成!
- 完成了。
- 这应该是在连接关闭之后。
我不是 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);
});
如果您正尝试使用 promises 包装现有的异步功能,这里的这个示例也可能有帮助:http://www.2ality.com/2014/10/es6-promises-api.html#example%3A_promisifying_xmlhttprequest
根据您需要的嵌套要求,您可能希望使用 promise 对象解析 promise;如果您需要一些说明,请随时发表评论:)
A way to say, ‘here’s the resolve value -- but when the chain is complete, also do this’?
不,本机承诺不提供此类功能。我会使用一个接受承诺返回回调的资源函数,回调会在连接打开时完成所有需要完成的事情(在链中)。资源管理器函数没有将 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(…)
详细说明 Bergi 的解决方案,这称为处置器模式。它以多种形式存在于多种语言中 - Python 中的 with
、C# 中的 using
和 Java 中的 try(){
资源。某些语言通过像 C# 这样的解构以本机方式处理范围内的资源。
一般的想法是用一个范围来封装一个值的生命周期。在您的情况下是数据库连接。它比必须在回调中调用 done
要简洁得多,因为忘记调用 done
会留下打开的连接和资源泄漏要容易得多。同步它看起来像:
function scope(cb){
try{
var conn = getConnection(...);
return cb(conn);
} finally {
conn.release();
}
}
promises 版本差别不大:
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
});
done() 函数的问题在于人们忘记调用它,从而导致泄漏。
我喜欢 Bergi 对传入回调的回答,因为它很干净,但不是很 "promise-y",而且仍然开放式不安全,例如如果人们串入永远不会解决回调承诺的承诺,那么它就会停止并泄漏。
这也是浏览器 API 中正在讨论的问题,我们正在考虑的一种模式是 return 和
AutoClosingPromise
AutoClosingPromise 就像一个承诺,但有两件事不同:
它"closes a ticket"(调用完成)在执行它的.then()之后。
此外,当它包含另一个承诺时,如果它从它的 .then() 中看到另一个 AutoClosingPromise return,那么它将它转发出去(传递那个承诺的票 - 一张不同的票- 从它自己的 .then() 函数 return 出来到 AutoClosingPromise。
第一部分意味着 API 可以 return 带有 "ticket" 的 AutoClosingPromise 保持资源打开(如打开计数)并确保票证将一旦第一个 .then() 函数被关闭 returns.
第二部分让调用者从即时的 .then() 函数中对 API 进行额外的异步调用,让 API 在工单重叠时保持资源打开时间.
它的一个特点是资源不会在常规承诺中提供,只有自动关闭的承诺,避免泄漏的风险。例如:
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;
}
};
和 fiddle:http://jsfiddle.net/jib1/w0ufvahL(需要一个理解 es6 箭头函数的浏览器,比如 Firefox,例如不是 Chrome)。
由于 API 控制所有发出票证的异步调用,这应该是相当防漏的。例如。即使调用者完全忽略 API 中的 returned 承诺,仍然会调用 close。
请注意,这是一个相当新的想法,而不是一个经过验证的结构,所以如果您最终使用它,请告诉我它是如何工作的。 ;-)
ES6 Promise 很棒。到目前为止,很容易调整我的想法 回调成语。我发现它自然地鼓励更多的模块化代码,并且 当然错误处理更清晰。
但有几次我遇到过看起来(?)不像他们那样的心流情况 可以很容易地从 nodebacks 转换为 promises(也许就是这样,但也许我只是对答案视而不见)。因为承诺是不可知的 关于下一个操作(或者如果有的话),似乎很难使用 API 的承诺不仅接受回调,而且 return 它们。
想到的最常见的例子是“完成”回调。它出现在数据库连接之类的东西中,表示“return 连接到池”,但我也看到它出现在很多其他地方。
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 中拥有的东西 首先,但它就在那里,有时你无法真正避免它。和 回调,您可以将“稍后调用”内部回调传递给原始的“外部” 打回来。它并不能完全将关注点分开,但在 至少它又快又简单。
是否有适合这种情况的基于 Promise 的方法?一种说法, “这是 resolve 值——但是当链条完成时,也这样做”?我 怀疑没有什么能完全符合我刚才描述的,因为它 真的不可能说链条已经“完成”,但也许我遗漏了一些 让您接近它而又不会弄得一团糟的模式...
编辑:根据目前的反馈,我意识到根本没有办法用真正的承诺来包装这样的 API,因为你 return 永远无法说出的承诺你对任何后续的链式承诺都有任何了解。但是你可以伪造它。扭曲的是结果相当脆弱。它必须假定唯一需要连接对象的 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));
控制台打印:
- 收到连接。
- 使用连接完成!
- 完成了。
- 这应该是在连接关闭之后。
我不是 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);
});
如果您正尝试使用 promises 包装现有的异步功能,这里的这个示例也可能有帮助:http://www.2ality.com/2014/10/es6-promises-api.html#example%3A_promisifying_xmlhttprequest
根据您需要的嵌套要求,您可能希望使用 promise 对象解析 promise;如果您需要一些说明,请随时发表评论:)
A way to say, ‘here’s the resolve value -- but when the chain is complete, also do this’?
不,本机承诺不提供此类功能。我会使用一个接受承诺返回回调的资源函数,回调会在连接打开时完成所有需要完成的事情(在链中)。资源管理器函数没有将 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(…)
详细说明 Bergi 的解决方案,这称为处置器模式。它以多种形式存在于多种语言中 - Python 中的 with
、C# 中的 using
和 Java 中的 try(){
资源。某些语言通过像 C# 这样的解构以本机方式处理范围内的资源。
一般的想法是用一个范围来封装一个值的生命周期。在您的情况下是数据库连接。它比必须在回调中调用 done
要简洁得多,因为忘记调用 done
会留下打开的连接和资源泄漏要容易得多。同步它看起来像:
function scope(cb){
try{
var conn = getConnection(...);
return cb(conn);
} finally {
conn.release();
}
}
promises 版本差别不大:
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
});
done() 函数的问题在于人们忘记调用它,从而导致泄漏。
我喜欢 Bergi 对传入回调的回答,因为它很干净,但不是很 "promise-y",而且仍然开放式不安全,例如如果人们串入永远不会解决回调承诺的承诺,那么它就会停止并泄漏。
这也是浏览器 API 中正在讨论的问题,我们正在考虑的一种模式是 return 和
AutoClosingPromise
AutoClosingPromise 就像一个承诺,但有两件事不同:
它"closes a ticket"(调用完成)在执行它的.then()之后。
此外,当它包含另一个承诺时,如果它从它的 .then() 中看到另一个 AutoClosingPromise return,那么它将它转发出去(传递那个承诺的票 - 一张不同的票- 从它自己的 .then() 函数 return 出来到 AutoClosingPromise。
第一部分意味着 API 可以 return 带有 "ticket" 的 AutoClosingPromise 保持资源打开(如打开计数)并确保票证将一旦第一个 .then() 函数被关闭 returns.
第二部分让调用者从即时的 .then() 函数中对 API 进行额外的异步调用,让 API 在工单重叠时保持资源打开时间.
它的一个特点是资源不会在常规承诺中提供,只有自动关闭的承诺,避免泄漏的风险。例如:
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;
}
};
和 fiddle:http://jsfiddle.net/jib1/w0ufvahL(需要一个理解 es6 箭头函数的浏览器,比如 Firefox,例如不是 Chrome)。
由于 API 控制所有发出票证的异步调用,这应该是相当防漏的。例如。即使调用者完全忽略 API 中的 returned 承诺,仍然会调用 close。
请注意,这是一个相当新的想法,而不是一个经过验证的结构,所以如果您最终使用它,请告诉我它是如何工作的。 ;-)