我試圖解決關于遞回和異步的編碼問題,但我對此有點卡住了,這就是問題所在:
任務
您必須執行許多任務。任務就是任何函式(通常是異步的)。
有些任務可以相互依賴。所以他們必須等到他們所依賴的任務首先完成。
您必須等到所有任務完成并回傳其結果。
輸入
以任務 ID 為鍵的物件和將任務描述為值的物件:
interface TaskDict {
[taskId: string]: {
dependencies: string[]; // an array of task ids.
task: (...dependencyResults: any[]) => any;
}
}
輸出
使用以任務 ID 作為鍵和任務結果作為值的物件決議的承諾:
interface TaskResultDict {
[taskId: string]: (
{
status: 'resolved',
value: any
} |
{
status: 'failed',
reason: any
} |
{
status: 'skipped',
unresolvedDependencies: string[]
}
);
}
請注意,如果任務的任何依賴項未解決(例如失敗或依次跳過),則不應執行任務。在這種情況下,狀態將為skipped。
skipped如果依賴項是回圈的,則狀態應該相同。是的,輸入中可能存在此錯誤。除此之外,輸入將始終有效(無需撰寫驗證)。
例子
const {deepStrictEqual} = require('assert');
const runTasks = (tasks: TaskDict): Promise<TaskResultDict> => {
// TODO
};
const taskResults = await runTasks({
a: {
dependencies: [],
task: () => Promise.resolve(4)
},
b: {
dependencies: ['a', 'c'],
task: async (a, c) => Math.sqrt(c * c - a * a)
},
c: {
dependencies: [],
task: () => new Promise((x) => setTimeout(x, 100)).then(() => 5)
},
d: {
dependencies: [],
task: () => Promise.reject('This will fail.')
},
e: {
dependencies: ['d', 'a', 'f'],
task: console.log
},
f: {
dependencies: ['f'],
task: () => console.log('Should never run - "f" depends on itself.')
}
});
deepStrictEqual(taskResults, {
a: {status: 'resolved', value: 4},
b: {status: 'resolved', value: 3},
c: {status: 'resolved', value: 5},
d: {status: 'failed', reason: 'This will fail.'},
e: {status: 'skipped', unresolvedDependencies: ['d', 'f']},
f: {status: 'skipped', unresolvedDependencies: ['f']}
});
當前方法
到目前為止,我已經采用了這種方法,但主要問題是,只要我不使用要解決的異步任務,邏輯就可以正常作業,在這種情況下,異步流沒有按我預期的那樣作業,因此依賴關系是執行某些任務之前已解決 未正確解決,您有任何線索嗎?
const resolveDependency = async (
taskId: string,
task: (...dependencyResults: any[]) => any,
dependencies: string[],
results: TaskResultDict
): Promise<TaskResultDict> => {
const unresolvedDependencies = Object.entries(results).filter(
(result) =>
dependencies.includes(result[0]) && result[1].status !== 'resolved'
);
try {
if (unresolvedDependencies.length > 0) {
return ({
[taskId]: {
status: 'skipped',
unresolvedDependencies: unresolvedDependencies.map(
(dependency) => dependency[0]
),
},
} as TaskResultDict);
}
const taskValue = await task(
...Object.entries(results)
.filter((result) => dependencies.includes(result[0]))
.map(
(result) => (result[1] as { status: 'resolved'; value: any }).value
)
);
return ({
[taskId]: {
status: 'resolved',
value: taskValue,
},
} as TaskResultDict);
} catch (error) {
return ({
[taskId]: {
status: 'failed',
reason: error,
},
} as TaskResultDict);
}
};
const runTaskWithDependencies = async (
tasks: TaskDict,
taskId: string,
results: TaskResultDict
): Promise<TaskResultDict> => {
const taskDependencies = tasks[taskId].dependencies;
const allDependenciesExecuted = Object.keys(results).length > 0 && Object.keys(results).every((taskId) =>
taskDependencies.includes(taskId)
);
if (taskDependencies.includes(taskId)) {
return {
[taskId]: {
status: 'skipped',
unresolvedDependencies: [taskId],
},
};
} else if (allDependenciesExecuted || taskDependencies.length === 0) {
const taskResult = await resolveDependency(
taskId,
tasks[taskId].task,
taskDependencies,
results
);
return {
...results,
...taskResult,
};
} else {
return (
await Promise.all(
taskDependencies
.map(
(dependency) =>
runTaskWithDependencies(tasks, dependency, results)
)
)
).reduce((previous, current) => {
return {
...previous,
...current,
};
}, {} as TaskResultDict);
}
};
export const runTasks = async (tasks: TaskDict): Promise<TaskResultDict> => {
const tasksIds = Object.keys(tasks);
return await tasksIds.reduce(async (previous, current) => {
const taskResult = await runTaskWithDependencies(
tasks,
current[0],
await previous
);
return {
...previous,
...taskResult,
};
}, Promise.resolve(<TaskResultDict>{}));
};
uj5u.com熱心網友回復:
任務必須根據它們的依賴關系按特定順序運行,但您的runTasks函式只是按照Object.keys(tasks).
一種方法可能是運行一批可運行的任務,直到不再有任務可運行,如果任務的所有依賴項都已運行,則該任務是可運行的。
我對您的型別進行了一些修改以簡化以下功能(仍然與您的代碼兼容):
interface Task {
dependencies: string[];
task: (...dependencyResults: any[]) => any;
}
interface TaskDict {
[taskId: string]: Task
}
interface ResolvedTask {
status: 'resolved',
value: any;
}
interface FailedTask {
status: 'failed';
reason: any;
}
interface SkippedTask {
status: 'skipped';
unresolvedDependencies: string[];
}
type TaskResult = ResolvedTask | FailedTask | SkippedTask;
interface TaskResultDict {
[taskId: string]: TaskResult;
}
現在,我們需要一個函式,它根據先前運行的任務的現有結果(如果有的話)運行一個任務(大致類似于您的runTaskWithDependencies):
const runTask = async (task: Task, results: TaskResultDict): Promise<TaskResult> => {
const unresolvedDependencies = task.dependencies.filter((dep) => !results[dep] || results[dep].status !== 'resolved');
if (unresolvedDependencies.length > 0) {
return { status: 'skipped', unresolvedDependencies };
}
const dependencyResults = task.dependencies.map((dep) => (results[dep] as ResolvedTask).value);
try {
const value = await task.task(...dependencyResults);
return { status: 'resolved', value };
} catch (reason) {
return { status: 'failed', reason };
}
};
最后,我們運行一批可運行的任務,直到沒有任務可以運行:
const runTasks = async (tasks: TaskDict): Promise<TaskResultDict> => {
let remainingTasks: [string, Task][] = Object.entries(tasks);
let results: TaskResultDict = {};
while (remainingTasks.length > 0) {
let runnableTasks = remainingTasks.filter(([_, { dependencies: deps }]) => deps.every((dep) => results.hasOwnProperty(dep)));
if (runnableTasks.length === 0) {
// there are remaining tasks but none of them are runnable: dependency issues, all will be skipped
runnableTasks = remainingTasks;
}
const newResultPromises = runnableTasks.map(([taskId, task]) => runTask(task, results).then((result) => ({ [taskId]: result })));
// await our batch:
const newResults = await Promise.all(newResultPromises);
// gather the results:
results = Object.assign(results, ...newResults);
remainingTasks = remainingTasks.filter(([taskId]) => !results.hasOwnProperty(taskId));
}
return results;
};

撰寫函式來檢查是否存在回圈依賴,它可以是一個簡單的遞回函式。您應該有一個 Set 來存盤已遍歷的節點,如果當前遍歷的節點在 Set 中,則意味著您具有回圈依賴關系。例如,在這種情況下,您可以拋出錯誤“檢測到回圈依賴”。
現在,撰寫函式來遍歷圖和鏈 Promise,但從底部開始,從“OUTPUT”節點開始,因為在這種情況下,您可以更輕松地鏈 Promise。最后,該函式應該回傳一個 Promise,所以你可以這樣做:
const chainPromises = async (graph) => {...} // 開始一切 await chainPromises();
例如:
- 第一個遍歷的節點是OUTPUT節點,它有兩個依賴項(D 和 E),所以它應該像 await Promise.all(promiseFromD(), promiseFromE()) 一樣等待
- 第二個遍歷的節點是 D 節點,它有兩個依賴項(A 和 B),所以它應該像 await Promise.all(promiseFromA(), promiseFromB()) 等等一樣等待......
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/346709.html
標籤:javascript 打字稿 异步 承诺
