JavaScript:无法使用 iterable 实现我自己的 Promise.all
JavaScript: having trouble to implement my own Promise.all with iterable
我正在尝试实施我自己的 Promise.all
来准备我的采访。
我的第一个版本是这个
Promise.all = function(promiseArray) {
return new Promise((resolve, reject) => {
try {
let resultArray = []
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(data => {
resultArray.push(data)
if (resultArray.length === length) {
resolve(resultArray)
}
}, reject)
}
}
catch(e) {
reject(e)
}
})
}
然而,原生 Promise.all
不仅接受数组,还接受任何可迭代对象,这意味着任何具有 Symbol.iterator
的对象,例如数组或 Set 或 Map。
所以像这样的东西将适用于本机 Promise.all
但不适用于我当前的实现
function resolveTimeout(value, delay) {
return new Promise((resolve) => setTimeout(resolve, delay, value))
}
const requests = new Map()
requests.set('reqA', resolveTimeout(['potatoes', 'tomatoes'], 1000))
requests.set('reqB', resolveTimeout(['oranges', 'apples'], 100))
Promise.all(requests.values()).then(console.log); // it works
我修改了我的 Promise.all
,首先添加一个检查以查看它是否具有 Symbol.iterator
以确保它是可迭代的。
Promise.all = function(promiseArray) {
if (!promiseArray[Symbol.iterator]) {
throw new TypeError('The arguments should be an iterable!')
}
return new Promise((resolve, reject) => {
try {
但挑战在于如何迭代可迭代对象。当前的实现是获取 if 的长度,并通过 const length = promiseArray.length
使用 for 循环来完成它,但是只有数组具有 length
属性,其他可迭代对象或迭代器如 Set
或Map.values()
将没有 属性 可用。
如何调整我的实现以支持其他类型的可迭代对象,例如原生 Promise.all
最简单和最实用的调整是将迭代结果复制到您控制的数组中。 Array.from
是一个很好的匹配,如 MDN(强调我的):
The Array.from()
static method creates a new, shallow-copied Array
instance from an array-like or iterable object.
[...]
Array.from() lets you create Arrays from:
- array-like objects (objects with a
length
property and indexed elements); or
- iterable objects (objects such as
Map
and Set
).
这也可能有助于为您的输入提供明确的索引,这些索引(如 derpirsher 在评论中提到的那样)对于确保您的 all
实施 returns 结果按顺序排列,即使它们可能乱序完成。
当然,当你这样做是为了更好地准备面试时,你可以选择避免像 Array.from
这样有用的功能,而倾向于使用基于 for ( ... of ... )
的自制实现来检查你的理解,正如 Mike 'Pomax' Kamermans 在评论中提到的那样。也就是说,一旦担任真正的开发角色,我希望您能尽可能多地使用 Array.from
(和内置的 Promise.all
)来减少重复和边缘情况。
But the challenge is with how to iterate through the iterable.
只需使用 for/of
来迭代可迭代对象。
Current implementation is to get the length of if and doing it with a for loop via const length = promiseArray.length however only arrays have length property on it, other iterables or iterators like Set or Map.values() will not have that property available.
只需计算您在 for/of
次迭代中获得了多少项。
下面是对实现的更详细的解释
如果您查看 Promise.all()
的 spec,它接受一个可迭代对象作为其参数。这意味着它必须具有适当的迭代器,以便您可以使用 for/of
对其进行迭代。如果您正在实施规范,则不必检查它是否是可迭代的。如果不是,实现将 throw
并因此拒绝并显示适当的错误(这是它应该做的)。 Promise 执行器已经为您捕获异常并拒绝。
正如其他地方提到的,您可以在可迭代对象上使用 Array.from()
从中生成一个实际的数组,但这似乎有点浪费,因为我们并不真正需要该数据的副本,我们只需要迭代它。而且,我们将同步迭代它,因此我们不必担心它在迭代过程中发生变化。
所以,使用 for/of
.
似乎是最有效的
用 .entries()
迭代可迭代对象会很好,因为这会给我们索引和值,然后我们可以使用索引来知道将此条目的结果放入 resultArray 的何处,但是似乎规范不需要对可迭代对象 .entries()
的支持,因此此实现只需要一个简单的带有 for (const p of promiseIterable)
的可迭代对象。该代码使用自己的计数器生成自己的索引以用于存储结果。
同样,我们需要生成 promise 的解析值,我们将返回一组与原始 promise 顺序相同的结果。
而且,可迭代对象不一定都是承诺 - 它也可以包含普通值(只是在结果数组中传递)所以我们需要确保我们也使用常规值。我们从原始数组中获得的值包装在 Promise.resolve()
中,以处理在源数组中传递纯值(不是承诺)的情况。
然后,最后,由于我们不能保证有一个 .length
属性,所以没有有效的方法可以提前知道可迭代对象中有多少项。我通过两种方式解决这个问题。首先,我在进入 cntr
变量时对项目进行计数,因此当我们完成 for/of
循环时,我们知道总共有多少项目。然后,随着 .then()
结果的出现,我们可以递减计数器并查看它何时归零以了解我们何时完成。
Promise.all = function(promiseIterable) {
return new Promise((resolve, reject) => {
const resultArray = [];
let cntr = 0;
for (const p of promiseIterable) {
// keep our own local index so we know where this result goes in the
// result array
let i = cntr;
// keep track of total number of items in the iterable
++cntr;
// track each promise - cast to a promise if it's not one
Promise.resolve(p).then(val => {
// store result in the proper order in the result array
resultArray[i] = val;
// if we have all results now, we are done and can resolve
--cntr;
if (cntr === 0) {
resolve(resultArray);
}
}).catch(reject);
}
// if the promiseIterable is empty, we need to resolve with an empty array
// as we could not have executed any of the body of the above for loop
if (cntr === 0) {
resolve(resultArray);
}
});
}
仅供参考,您可以在 Promise.all()
here. Also, make sure to look at PerformPromiseAll
.
的 ECMAScript 规范中看到其中的一些逻辑
我正在尝试实施我自己的 Promise.all
来准备我的采访。
我的第一个版本是这个
Promise.all = function(promiseArray) {
return new Promise((resolve, reject) => {
try {
let resultArray = []
const length = promiseArray.length
for (let i = 0; i <length; i++) {
promiseArray[i].then(data => {
resultArray.push(data)
if (resultArray.length === length) {
resolve(resultArray)
}
}, reject)
}
}
catch(e) {
reject(e)
}
})
}
然而,原生 Promise.all
不仅接受数组,还接受任何可迭代对象,这意味着任何具有 Symbol.iterator
的对象,例如数组或 Set 或 Map。
所以像这样的东西将适用于本机 Promise.all
但不适用于我当前的实现
function resolveTimeout(value, delay) {
return new Promise((resolve) => setTimeout(resolve, delay, value))
}
const requests = new Map()
requests.set('reqA', resolveTimeout(['potatoes', 'tomatoes'], 1000))
requests.set('reqB', resolveTimeout(['oranges', 'apples'], 100))
Promise.all(requests.values()).then(console.log); // it works
我修改了我的 Promise.all
,首先添加一个检查以查看它是否具有 Symbol.iterator
以确保它是可迭代的。
Promise.all = function(promiseArray) {
if (!promiseArray[Symbol.iterator]) {
throw new TypeError('The arguments should be an iterable!')
}
return new Promise((resolve, reject) => {
try {
但挑战在于如何迭代可迭代对象。当前的实现是获取 if 的长度,并通过 const length = promiseArray.length
使用 for 循环来完成它,但是只有数组具有 length
属性,其他可迭代对象或迭代器如 Set
或Map.values()
将没有 属性 可用。
如何调整我的实现以支持其他类型的可迭代对象,例如原生 Promise.all
最简单和最实用的调整是将迭代结果复制到您控制的数组中。 Array.from
是一个很好的匹配,如 MDN(强调我的):
The
Array.from()
static method creates a new, shallow-copiedArray
instance from an array-like or iterable object.[...]
Array.from() lets you create Arrays from:
- array-like objects (objects with a
length
property and indexed elements); or- iterable objects (objects such as
Map
andSet
).
这也可能有助于为您的输入提供明确的索引,这些索引(如 derpirsher 在评论中提到的那样)对于确保您的 all
实施 returns 结果按顺序排列,即使它们可能乱序完成。
当然,当你这样做是为了更好地准备面试时,你可以选择避免像 Array.from
这样有用的功能,而倾向于使用基于 for ( ... of ... )
的自制实现来检查你的理解,正如 Mike 'Pomax' Kamermans 在评论中提到的那样。也就是说,一旦担任真正的开发角色,我希望您能尽可能多地使用 Array.from
(和内置的 Promise.all
)来减少重复和边缘情况。
But the challenge is with how to iterate through the iterable.
只需使用 for/of
来迭代可迭代对象。
Current implementation is to get the length of if and doing it with a for loop via const length = promiseArray.length however only arrays have length property on it, other iterables or iterators like Set or Map.values() will not have that property available.
只需计算您在 for/of
次迭代中获得了多少项。
下面是对实现的更详细的解释
如果您查看 Promise.all()
的 spec,它接受一个可迭代对象作为其参数。这意味着它必须具有适当的迭代器,以便您可以使用 for/of
对其进行迭代。如果您正在实施规范,则不必检查它是否是可迭代的。如果不是,实现将 throw
并因此拒绝并显示适当的错误(这是它应该做的)。 Promise 执行器已经为您捕获异常并拒绝。
正如其他地方提到的,您可以在可迭代对象上使用 Array.from()
从中生成一个实际的数组,但这似乎有点浪费,因为我们并不真正需要该数据的副本,我们只需要迭代它。而且,我们将同步迭代它,因此我们不必担心它在迭代过程中发生变化。
所以,使用 for/of
.
用 .entries()
迭代可迭代对象会很好,因为这会给我们索引和值,然后我们可以使用索引来知道将此条目的结果放入 resultArray 的何处,但是似乎规范不需要对可迭代对象 .entries()
的支持,因此此实现只需要一个简单的带有 for (const p of promiseIterable)
的可迭代对象。该代码使用自己的计数器生成自己的索引以用于存储结果。
同样,我们需要生成 promise 的解析值,我们将返回一组与原始 promise 顺序相同的结果。
而且,可迭代对象不一定都是承诺 - 它也可以包含普通值(只是在结果数组中传递)所以我们需要确保我们也使用常规值。我们从原始数组中获得的值包装在 Promise.resolve()
中,以处理在源数组中传递纯值(不是承诺)的情况。
然后,最后,由于我们不能保证有一个 .length
属性,所以没有有效的方法可以提前知道可迭代对象中有多少项。我通过两种方式解决这个问题。首先,我在进入 cntr
变量时对项目进行计数,因此当我们完成 for/of
循环时,我们知道总共有多少项目。然后,随着 .then()
结果的出现,我们可以递减计数器并查看它何时归零以了解我们何时完成。
Promise.all = function(promiseIterable) {
return new Promise((resolve, reject) => {
const resultArray = [];
let cntr = 0;
for (const p of promiseIterable) {
// keep our own local index so we know where this result goes in the
// result array
let i = cntr;
// keep track of total number of items in the iterable
++cntr;
// track each promise - cast to a promise if it's not one
Promise.resolve(p).then(val => {
// store result in the proper order in the result array
resultArray[i] = val;
// if we have all results now, we are done and can resolve
--cntr;
if (cntr === 0) {
resolve(resultArray);
}
}).catch(reject);
}
// if the promiseIterable is empty, we need to resolve with an empty array
// as we could not have executed any of the body of the above for loop
if (cntr === 0) {
resolve(resultArray);
}
});
}
仅供参考,您可以在 Promise.all()
here. Also, make sure to look at PerformPromiseAll
.