我在内存中实现数据结构 阴影部分大型数据结构存储在网络上的某个地方。 让我们说有问题的数据结构是二叉树。 我希望内存中的树最初只包含根节点, 当用户(或算法)探索时,它应该根据需要从网络中提取节点而变得懒散。
执行此操作的一种自然方法是树节点数据类型提供方法getLeftChild()
,getRightChild()
,
每个子节点立即返回 promise 。
在左子节点已在内存中的树节点上调用getLeftChild()
时,
它返回一个已经使用缓存子进行解析的promise;
否则它会启动一个孩子的获取(如果尚未通过之前的调用启动)并返回一个承诺,
当被抓住的孩子最终从网上回来时,被取出的孩子将被保存在记忆中,以便将来用来解决这个承诺。
因此,要在左侧分支下打印节点5级,我会说:
root.getLeftChild()
.then(child0 => child0.getLeftChild())
.then(child00 => child00.getLeftChild())
.then(child000 => child000.getLeftChild())
.then(child0000 => child0000.getLeftChild())
.then(child00000 => {
console.log("child00000 = ", child00000);
});
或(感谢@Thomas):
const lc = node => node.getLeftChild();
Promise.resolve(root)
.then(lc).then(lc).then(lc).then(lc).then(lc)
.then(child00000 => {
console.log("child00000 = ", child00000);
});
或者,使用async/await
同样的事情:
(async()=>{
let child0 = await root.getLeftChild();
let child00 = await child0.getLeftChild();
let child000 = await child00.getLeftChild();
let child0000 = await child000.getLeftChild();
let child00000 = await child0000.getLeftChild();
console.log("child00000 = ",child00000);
})();
这一切都很好,在任何一种情况下,调用代码看起来都不太可能。
我唯一的疑惑是,在二叉树的部分内部(或任何类似的链接数据结构)进行探索时 已经在记忆中,我不想承受启动新微任务的开销 每次我想从内存数据结构中的一个节点转到邻居。 考虑一种算法,其核心计算可以进行数百万次这样的链接跟踪操作。
对于每个then
回调执行,Promises/A+确实需要一个新的微任务(至少):
2.2.4 onFulfilled或onRejected在执行上下文堆栈仅包含平台代码之前不得调用。 [3.1]。
我相信async/await
有类似的要求。
我想知道的是:什么是最简单/最干净的方法
一个类似Promise的对象,其行为与Promises / A + promise完全相同,除了以用于第2.2.4节?
即我希望它有一个then
(或then
- 类似)方法,即"同步时可用",以便上面的第一个代码段
将在不产生执行上下文的情况下一次执行。
为了避免命名问题/混淆,我很高兴不要在我的同步访问时调用then
(由于Promises / A +,现在实际上是一个保留词);相反,我会称之为thenOrNow
。
我会调用我的假设类型/实现PromiseOrNow
。
我是否必须从头开始编写PromiseOrNow
,或者是否有一种利用现有Promise / A +实施的简洁可靠的方法,例如本地Promise
?
请注意,由于我不打算捣乱任何名为" then
"的任何内容,
PromiseOrNow
偶然可能是Promises / A +,如果这是一个很好的方式。
也许它将是原生Promise.prototype
的原型。
这些属性在某些方面会很好,但它们不是要求。
答案 0 :(得分:1)
您可以使用thenOrNow
方法使用以下包装函数扩展标准promise:
function addThenOrNow(p) {
let value, resolved;
p.then( response => (value = response, resolved = 1) )
.catch( err => (value = err, resolved = -1) );
p.thenOrNow = (fulfilled, rejected) =>
resolved > 0 ? Promise.resolve(fulfilled ? fulfilled(value) : value)
: resolved ? Promise.reject (rejected ? rejected (value) : value)
: p.then(fulfilled, rejected); // default then-behaviour
return p;
}
// Demo
const wait = ms => new Promise( resolve => setTimeout(resolve, ms) );
const addSlow = (a, b) => wait(100).then(_ => a + b);
const prom = addThenOrNow(addSlow(2, 3));
prom.then(value => console.log('promise for adding 2 and 3 resolved with', value));
setTimeout(_ => {
// At this time the promise has been resolved.
let sum;
prom.then(response => sum = response);
// above callback was executed asynchronously
console.log('sum after calling .then is', sum);
prom.thenOrNow(response => sum = response);
// above callback was executed synchronously
console.log('sum after calling .thenOrNow is', sum);
}, 200);
您可以创建自己的myPromise构造函数,而不是使用包装器,但主要逻辑是相同的。
如果promise以异步方式解析 (即在您对原始承诺调用thenOrNow
之后),addThenOrNow
的上述实现将只能同步执行回调,例如会是你的情况(假设您的http请求是异步执行的)。但是,如果promise立即(同步)解析,thenOrNow
将无法通过本机Promise接口同步获取值。
bluebird
等其他库为synchronous inspection提供了方法,因此,如果您包含bluebird
,则可以提供一种解决方案,该解决方案也可用于立即解决承诺:
function addThenOrNow(p) {
p.thenOrNow = (fulfilled, rejected) =>
p.isFulfilled() ? Promise.resolve(fulfilled ? fulfilled(p.value()) : p.value())
: p.isRejected()? Promise.reject (rejected ? rejected (p.reason()) : p.reason())
: p.then(fulfilled, rejected); // default then-behaviour
return p;
}
// Demo
const prom = addThenOrNow(Promise.resolve(2+3));
let sum;
prom.then(response => sum = response);
console.log('sum after calling then is', sum);
prom.thenOrNow(response => sum = response);
console.log('sum after calling thenOrNow is', sum);
<script src="https://cdnjs.cloudflare.com/ajax/libs/bluebird/3.5.0/bluebird.min.js"></script>
但同样,由于您的场景本质上是异步的(从HTTP请求中获取响应),您可以使用任一解决方案。
答案 1 :(得分:0)
我不想承受每次启动新微任务的开销
他们被称为&#34; microtask&#34;因为它们有微观的开销。微任务队列非常快,你不必担心。更好keep consistency而不是为Zalgo而堕落。
制作类似Promise的对象最简单/最干净的方法是什么,其行为与Promises / A +承诺完全相同,但2.2.4除外?
使用具有此功能的现有实现。例如,Q v0.6 contained an asap
method。
我是否必须从头开始编写PromiseOrNow
不,你可以从适合你的Promise库开始。
是否有一种利用现有Promise / A +实施的简洁可靠的方法,如本机Promise?
不,或者至少不是来自其公共API,因为它没有同步检查功能。
答案 2 :(得分:0)
抱歉延迟,但我很忙。如何通过不同的方法来解决实际问题?与其尝试以同步方式解析Promise,而不是几分钟,如何分离任务的同步和异步部分。
准确地说:这里的异步部分是加载二叉树中特定节点的数据。即使树是懒惰的,颠倒也不一定是异步的。
因此,我们可以从异步数据加载中去除树的转换和延迟生成。
//sync traversion:
var node = root.getOrCreate('left', 'right', 'right', 'left', 'right');
//wich is a shorthand for the more verbose:
var child0 = root.getOrCreateLeft();
var child01 = child0.getOrCreateRight();
var child011 = child01.getOrCreateRight();
var child0110 = child011.getOrCreateLeft();
var node = child0110.getOrCreateRight();
到目前为止,一切都是(虽然是懒惰的)良好的老式同步代码。现在是异步部分,访问节点的数据。
node.then(nodeData => console.log("data:", nodeData));
//or even
var nodeData = await node;
console.log(nodeData);
//or
var data = await root.getOrCreate('left', 'right', 'right', 'left', 'right');
到实施:
class AsyncLazyBinaryTree {
constructor(config, parent=null){
if(typeof config === "function")
config = {load: config};
//tree strucute
this.parent = parent;
this.left = null;
this.right = null;
//data-model & payload
this.config = config;
this._promise = null;
//start loading the data
if(config.lazy || config.lazy === undefined)
this.then();
}
get root(){
for(var node = this, parent; parent=node.parent; node = parent);
return node;
}
///// These methods are responsible for the LAZY nature of this tree /////
getOrCreateLeft(){ return _goc(this, "left") }
getOrCreateRight(){ return _goc(this, "right") }
getOrCreate(...args){
if(args.length === 1 && Array.isArray(args[0]))
args = args[0];
var invalid = args.find(arg => arg !== "left" && arg !== "right");
if(invalid)
throw new Error("invalid argument "+ invalid);
for(var node = this, i=0; i<args.length; ++i)
node = _goc(node, args[i]);
return node;
}
///// These methods are responsible for the ASYNC nature of this tree /////
//If this node looks like a promise, quacks like a promise, walks like a promise, ...
//you can use it as a Promise of the data they represent
then(a,b){
if(!this._promise){
this._promise = Promise.resolve().then(() => this.config.load(this));
}
return this._promise.then(a,b);
}
catch(fn){ return this.then(null, fn); }
//to force the node to reload the data
//can be used as `node.invalidate().then(...)`
//or `await node.invalidate()`
invalidate(){
this._promise = null;
return this;
}
}
//private
function _goc(node, leftOrRight){
if(!node[leftOrRight])
node[leftOrRight] = new AsyncLazyBinaryTree(node.config, node);
return node[leftOrRight];
}
一个基本的例子
//A utility to delay promise chains.
/* use it as: somePromise.then(wait(500)).then(...)
or wait(500).then(...);
or wait(500).resolve(value).then(...)
or wait(500).reject(error).then(...);
*/
var wait = ((proto) => delay => Object.assign(value => new Promise(resolve => setTimeout(resolve, delay, value)), proto))({
then(a,b){ return this().then(a,b) },
resolve(value){ return this(value) },
reject(error){ return this(error).then(Promise.reject.bind(Promise)) }
});
//initializing a tree
var root = new AsyncLazyBinaryTree({
//load the data as soon as the Node is generated
lazy: true,
//this method will be called (once) for each node that needs its data.
load(node){
var path = this.getPath(node);
console.log('load', path, node);
//create an artificial delay, then return the payload
return wait(1500).resolve({
ts: new Date(),
path: path
});
//but maybe you need some data from the parentNode, to actually load/generate the current data:
//node.parent is `null` for the root node,
//that's why I wrap that into a Promise.resolve()
//so for the rootNode, parentData is now null;
return Promise.resolve(node.parent)
.then(parentData => {
//do something with the parentData
return wait(500).resolve({
ts: new Date(),
path: path,
parent: parentData,
});
});
},
//an utility to be used by load().
//the tree doesn't care if you add methods or data to the config
//it's all passed through the whole tree.
getPath(node){
var path = "";
for(var n = node, p; p = n.parent; n=p){
var leftOrRight = n === p.left? "left": n === p.right? "right": "";
if(!leftOrRight) throw new Error("someone messed up the tree");
path = "." + leftOrRight + path;
}
return "root" + path;
},
});
var node = root.getOrCreate("left", "right", "left");
//just to be perfectly clear
//the config is simply passed through the tree, and you can (ab)use it to store additional data about the tree.
console.log("same config", root.config === node.config);
node.then(nodeData => console.log("data:", nodeData));
我不擅长编写例子。与课程一起玩,并根据需要修改/扩展