javascript - Promise then() 方法没有按预期触发?
问题描述
在下面的代码中,我希望mark-1
在mark-2
. 我猜我在某些地方没有正确await
使用。async
我很确定这条线:
const things = await fs.promises.readdir(folder);
这条线
const stats = await fs.promises.stat(path);
是正确的,因为我们正在等待文件系统响应。
到目前为止,我还不关心错误检查或跨平台代码,只是让承诺正常工作
// Libraries
const fs = require('fs');
// API
getThings('_t/').then(() => {
console.log('mark-2')
})
// Accesses the file system and returns an array of files / folders
async function getThings (folder) {
const things = await fs.promises.readdir(folder);
things.forEach(async (thing)=>{
await getStats(thing, folder);
});
}
// Gets statistics for each file/folder
async function getStats (thing, folder) {
const path = folder + thing;
const stats = await fs.promises.stat(path);
console.log('mark-1');
}
解决方案
问题是您正在使用async
andawait
在forEach
通话中,这并不像您期望的那样工作。
该forEach
方法并不真正关心回调函数的返回值(在本例中是getStats
返回的承诺)。
你应该承诺map
的数组,并使用:things
Promise.all
async function getThings (folder) {
const things = await fs.promises.readdir(folder);
const promises = things.map(thing => getStats(thing, folder));
return await Promise.all(promises);
}
请注意,这将“并行”执行 Promise,而不是按顺序执行。
如果您想逐个执行承诺,您可以“减少”承诺数组,或使用常规循环(for
, for-of
)。
编辑:
让我尝试澄清为什么使用带有 forEach 的异步回调函数不起作用。
尝试#1:脱糖:
在原始示例中,我们有这样的内容:
// ...
things.forEach(async (thing)=>{
await getStats(thing, folder);
});
// ...
如果我们将回调与 分开forEach
,我们有:
const callback = async (thing)=>{
await getStats(thing, folder);
};
things.forEach(callback);
如果我们“去糖化”异步函数:
const callback = function (thing) {
return new Promise((resolve, reject) => {
try {
getStats(thing, folder).then(stat => /*do nothing*/);
} catch (err) {
reject(err);
}
resolve();
});
};
things.forEach(callback);
用 标记函数async
确保函数将始终返回一个承诺,无论其完成,如果函数在没有显式返回值的情况下执行,则承诺将用 解决undefined
,如果它返回某个值,则承诺将解决它,最后,如果函数中的某些东西抛出,则 promise 将被拒绝。
如您所见,问题在于承诺没有被任何东西等待,而且它们也没有解决任何价值。放置在回调中的 await 实际上对值没有任何作用,就像在上面我正在做.then
并且对值什么都不做一样。
尝试2:实现一个简单的forEach
功能:
function forEach(array, callback) {
for(const value of array) {
callback(value);
}
}
const values = [ 'a', 'b', 'c' ];
forEach(values, (item) => {
console.log(item)
});
上面的 forEach 是对Array.prototype.forEach
方法的过度简化,只是为了展示它的结构,实际上回调函数被称为传递数组作为this
值,并传递三个参数,当前元素,当前索引,再次是数组实例,但是我们明白了。
如果我们想实现一个async
forEach
函数,我们将不得不等待回调调用:
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
async function forEachAsync(array, callback) {
for(const value of array) {
await callback(value);
}
}
(async () => {
await forEachAsync(values, async (item) => {
console.log(await sleep(item.time, item.value))
});
console.log('done');
})()
上面的forEachAsync
函数将按顺序逐项迭代和等待,通常你不希望这样,如果异步函数是独立的,它们可以并行完成,就像我首先建议的那样。
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
(async () => {
const promises = values.map(item => sleep(item.time, item.value));
const result = await Promise.all(promises);
console.log(result);
})()
如您所见,即使 Promise 是并行执行的,我们也会以与数组中的 Promise 相同的顺序获得结果。
但是这个例子和第一个例子的区别在于这个例子只需要 300ms(最长的 promise to resolve),而第一个需要 600ms(300ms + 200ms + 100ms)。
希望它使它更清楚。
推荐阅读
- php - PHP - 未捕获的错误:在布尔值上调用成员函数 *()
- xml - 春季无法访问 XML 文件
- sql - 根据 Where 子句将 SQL Server 行复制到新行
- matlab - matlab:视频中的颜色对象跟踪和位置
- vuejs2 - Bulma 导航栏和 VueJS 路由器活动链接
- reactjs - 使 React 属性不绑定到特定变量
- postgresql - Postgres 自定义浮点类型,始终在点后截断为 2 位小数
- javascript - 使用 Lodash 将字符串数组转换为对象数组
- postgresql - PostgreSQL 何时对双精度列类型进行舍入
- javascript - 如何使用 jsPDF 将图像添加到 PDF,以便它在每台机器上显示相同?