在主范围内使用由自定义函数触发的异步迭代器
Use async iterator triggered by a custom function in main scope
我想做的是创建一个迭代器,它只在调用外部函数时触发,比如外部事件。
一个只等待自定义事件的迭代器。
function createIteratorWithFunction() {
var thingThatResolves;
var asyncIterable = {
thingThatResolves,
[Symbol.asyncIterator]() {
return {
next() {
return (new Promise((resolve, reject) => asyncIterable.thingThatResolves = (resolve))).then(_ => ({
value: _,
done: false
}));
},
return () {
return {
done: true
}
}
};
}
};
return asyncIterable;
}
iter = createIteratorWithFunction();
(async function() {
for await (let val of iter) {
console.log(val);
}
})()
<button onclick="iter.thingThatResolves('execute');iter.thingThatResolves(3)">execute next!</button>
如你所见,它只解析了'execute',没有解析3,当然因为promises不能解析多次,而且只是异步更新,这个我理解,但是自从迭代器是异步的,我将如何创建一个队列,以便 next()
也可以检索任何可能被同步触发的值?
我觉得有一个涉及承诺链的更优雅的解决方案,但目前它正在逃避我。 :-) 查看内联评论:
function createIteratorWithFunction() {
// Our pending promise, if any
let promise = null;
// The `resolve` function for our `pending` promise
let resolve = null;
// The values in the queue
const values = [];
// The async iterable
const asyncIterable = {
add(value) {
// Add a value to the queue; if there's a pending promise, fulfill it
values.push(value);
const r = resolve;
resolve = pending = null;
r?.();
},
[Symbol.asyncIterator]() {
return {
async next() {
// If we don't have a value...
while (!values.length) {
// ...we need to wait for one; make sure we have something
// to wait for
if (!resolve) {
pending = new Promise(r => { resolve = r; });
}
await pending;
}
// Get the value we waited for and return it
const value = values.shift();
return {
value,
done: false,
};
},
return() {
return {
done: true,
};
}
};
}
};
return asyncIterable;
}
const iter = createIteratorWithFunction();
(async function() {
for await (let val of iter) {
console.log(val);
}
})();
document.getElementById("execute").addEventListener("click", () => {
iter.add("execute");
iter.add(3);
});
<button id="execute">execute next!</button>
这里的关键之一是异步可迭代对象可以有重叠的迭代,它不能因此而混淆。此实现通过创建它会在需要时同步等待的承诺来避免这种情况。
function createIteratorWithFunction() {
// Our pending promise, if any
let promise = null;
// The `resolve` function for our `pending` promise
let resolve = null;
// The values in the queue
const values = [];
// The async iterable
const asyncIterable = {
add(value) {
// Add a value to the queue; if there's a pending promise, fulfill it
values.push(value);
const r = resolve;
resolve = pending = null;
r?.();
},
[Symbol.asyncIterator]() {
return {
async next() {
// If we don't have a value...
while (!values.length) {
// ...we need to wait for one; make sure we have something
// to wait for
if (!resolve) {
pending = new Promise(r => { resolve = r; });
}
await pending;
}
// Get the value we waited for and return it
const value = values.shift();
return {
value,
done: false,
};
},
return() {
return {
done: true,
};
}
};
}
};
return asyncIterable;
}
const iter = createIteratorWithFunction();
(async function() {
for await (let val of iter) {
console.log("first:", val);
}
})();
(async function() {
for await (let val of iter) {
console.log("second:", val);
}
})();
document.getElementById("execute").addEventListener("click", () => {
iter.add("execute");
iter.add(3);
});
<button id="execute">execute next!</button>
当我必须让 promise 的 resolve
函数可以在 promise 执行器函数(您传递的函数 new Promise
)之外访问时,我永远不会开心,但正如我所说,优雅的解决方案承诺链正在逃避我。我强烈地感觉到它在那里……某处……:-)
另一种想法和方法,您可以使用自定义事件,一个优点是代码更容易推理。
下面我敲了一个简单的例子,它也可以让你取消迭代器和处理错误。 makeIter
简单给你4个功能,
- add = 使用它向迭代器添加一个项目。
- iter = 这是您可以
for await
使用的迭代器。
- done = 如果已经完成,您可以调用它并让 GC 执行它。
- error = 允许您将错误放入迭代器,您可以通过 un-commenting 最后一行进行测试。
为了防止任何竞争条件,我只是使用了一个堆栈..
function makeIter() {
const obj = new EventTarget();
const evName = 'my-iter';
const stack = [];
obj.addEventListener(evName, e => {
stack.push(e.detail);
resolve();
});
async function *iter() {
while (true) {
await new Promise(r => resolve = r);
while (stack.length) {
const s = stack.shift();
if (s.resolve) yield(s.resolve);
if (s.reject) throw s.reject;
if (s.cancel) return;
}
}
}
function ev(p) {
obj.dispatchEvent(new CustomEvent(evName, {detail:p}));
}
return {
error: (e) => ev({reject: e}),
done: () => ev({cancel: true}),
add: item => ev({resolve: item}),
iter: iter()
}
}
///testing...
const test = makeIter();
(async function () {
try {
for await (const item of test.iter) {
console.log(item);
}
} finally {
console.log('iter done');
}
}());
test.add('hello');
setTimeout(() => test.add('there'), 100);
setTimeout(() => {test.add('1'); test.add('2'); test.add('3'); }, 200);
setTimeout(() => test.add('4'), 400);
setTimeout(() => test.done(), 1000);
//setTimeout(() => test.error(new Error('oops')), 1000);
我想做的是创建一个迭代器,它只在调用外部函数时触发,比如外部事件。
一个只等待自定义事件的迭代器。
function createIteratorWithFunction() {
var thingThatResolves;
var asyncIterable = {
thingThatResolves,
[Symbol.asyncIterator]() {
return {
next() {
return (new Promise((resolve, reject) => asyncIterable.thingThatResolves = (resolve))).then(_ => ({
value: _,
done: false
}));
},
return () {
return {
done: true
}
}
};
}
};
return asyncIterable;
}
iter = createIteratorWithFunction();
(async function() {
for await (let val of iter) {
console.log(val);
}
})()
<button onclick="iter.thingThatResolves('execute');iter.thingThatResolves(3)">execute next!</button>
如你所见,它只解析了'execute',没有解析3,当然因为promises不能解析多次,而且只是异步更新,这个我理解,但是自从迭代器是异步的,我将如何创建一个队列,以便 next()
也可以检索任何可能被同步触发的值?
我觉得有一个涉及承诺链的更优雅的解决方案,但目前它正在逃避我。 :-) 查看内联评论:
function createIteratorWithFunction() {
// Our pending promise, if any
let promise = null;
// The `resolve` function for our `pending` promise
let resolve = null;
// The values in the queue
const values = [];
// The async iterable
const asyncIterable = {
add(value) {
// Add a value to the queue; if there's a pending promise, fulfill it
values.push(value);
const r = resolve;
resolve = pending = null;
r?.();
},
[Symbol.asyncIterator]() {
return {
async next() {
// If we don't have a value...
while (!values.length) {
// ...we need to wait for one; make sure we have something
// to wait for
if (!resolve) {
pending = new Promise(r => { resolve = r; });
}
await pending;
}
// Get the value we waited for and return it
const value = values.shift();
return {
value,
done: false,
};
},
return() {
return {
done: true,
};
}
};
}
};
return asyncIterable;
}
const iter = createIteratorWithFunction();
(async function() {
for await (let val of iter) {
console.log(val);
}
})();
document.getElementById("execute").addEventListener("click", () => {
iter.add("execute");
iter.add(3);
});
<button id="execute">execute next!</button>
这里的关键之一是异步可迭代对象可以有重叠的迭代,它不能因此而混淆。此实现通过创建它会在需要时同步等待的承诺来避免这种情况。
function createIteratorWithFunction() {
// Our pending promise, if any
let promise = null;
// The `resolve` function for our `pending` promise
let resolve = null;
// The values in the queue
const values = [];
// The async iterable
const asyncIterable = {
add(value) {
// Add a value to the queue; if there's a pending promise, fulfill it
values.push(value);
const r = resolve;
resolve = pending = null;
r?.();
},
[Symbol.asyncIterator]() {
return {
async next() {
// If we don't have a value...
while (!values.length) {
// ...we need to wait for one; make sure we have something
// to wait for
if (!resolve) {
pending = new Promise(r => { resolve = r; });
}
await pending;
}
// Get the value we waited for and return it
const value = values.shift();
return {
value,
done: false,
};
},
return() {
return {
done: true,
};
}
};
}
};
return asyncIterable;
}
const iter = createIteratorWithFunction();
(async function() {
for await (let val of iter) {
console.log("first:", val);
}
})();
(async function() {
for await (let val of iter) {
console.log("second:", val);
}
})();
document.getElementById("execute").addEventListener("click", () => {
iter.add("execute");
iter.add(3);
});
<button id="execute">execute next!</button>
当我必须让 promise 的 resolve
函数可以在 promise 执行器函数(您传递的函数 new Promise
)之外访问时,我永远不会开心,但正如我所说,优雅的解决方案承诺链正在逃避我。我强烈地感觉到它在那里……某处……:-)
另一种想法和方法,您可以使用自定义事件,一个优点是代码更容易推理。
下面我敲了一个简单的例子,它也可以让你取消迭代器和处理错误。 makeIter
简单给你4个功能,
- add = 使用它向迭代器添加一个项目。
- iter = 这是您可以
for await
使用的迭代器。 - done = 如果已经完成,您可以调用它并让 GC 执行它。
- error = 允许您将错误放入迭代器,您可以通过 un-commenting 最后一行进行测试。
为了防止任何竞争条件,我只是使用了一个堆栈..
function makeIter() {
const obj = new EventTarget();
const evName = 'my-iter';
const stack = [];
obj.addEventListener(evName, e => {
stack.push(e.detail);
resolve();
});
async function *iter() {
while (true) {
await new Promise(r => resolve = r);
while (stack.length) {
const s = stack.shift();
if (s.resolve) yield(s.resolve);
if (s.reject) throw s.reject;
if (s.cancel) return;
}
}
}
function ev(p) {
obj.dispatchEvent(new CustomEvent(evName, {detail:p}));
}
return {
error: (e) => ev({reject: e}),
done: () => ev({cancel: true}),
add: item => ev({resolve: item}),
iter: iter()
}
}
///testing...
const test = makeIter();
(async function () {
try {
for await (const item of test.iter) {
console.log(item);
}
} finally {
console.log('iter done');
}
}());
test.add('hello');
setTimeout(() => test.add('there'), 100);
setTimeout(() => {test.add('1'); test.add('2'); test.add('3'); }, 200);
setTimeout(() => test.add('4'), 400);
setTimeout(() => test.done(), 1000);
//setTimeout(() => test.error(new Error('oops')), 1000);