1
0
mirror of https://gitee.com/coder-xiaomo/leetcode-problemset synced 2025-01-11 02:58:13 +08:00
Code Issues Projects Releases Wiki Activity GitHub Gitee
leetcode-problemset/leetcode-cn/originData/design-cancellable-function.json

63 lines
19 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"data": {
"question": {
"questionId": "2788",
"questionFrontendId": "2650",
"categoryTitle": "JavaScript",
"boundTopicId": 2238365,
"title": "Design Cancellable Function",
"titleSlug": "design-cancellable-function",
"content": "<p>Sometimes you have a long running task, and you may wish to cancel it before it completes. To help with this goal, write a function&nbsp;<code>cancellable</code> that accepts a generator object and returns an array of two values: a <strong>cancel function</strong> and a <strong>promise</strong>.</p>\n\n<p>You may assume the generator function will only&nbsp;yield promises. It is your function&#39;s responsibility to pass the values resolved by the promise back to the generator. If the promise rejects, your function should throw that&nbsp;error back to the generator.</p>\n\n<p>If the cancel callback is called before the generator is done, your function should throw an error back to the generator. That error should be the string&nbsp;<code>&quot;Cancelled&quot;</code>&nbsp;(Not an <code>Error</code>&nbsp;object). If the error was caught, the returned&nbsp;promise should resolve with the next value that was yielded or returned. Otherwise, the promise should reject with the thrown error. No more code should be executed.</p>\n\n<p>When the generator is done, the promise your function returned should resolve the value the generator returned. If, however, the generator throws an error, the returned promise should reject with the error.</p>\n\n<p>An example of how your code would be used:</p>\n\n<pre>\nfunction* tasks() {\n const val = yield new Promise(resolve =&gt; resolve(2 + 2));\n yield new Promise(resolve =&gt; setTimeout(resolve, 100));\n return val + 1; // calculation shouldn&#39;t be done.\n}\nconst [cancel, promise] = cancellable(tasks());\nsetTimeout(cancel, 50);\npromise.catch(console.log); // logs &quot;Cancelled&quot; at t=50ms\n</pre>\n\n<p>If&nbsp;instead&nbsp;<code>cancel()</code> was not called or was called after <code>t=100ms</code>, the promise would&nbsp;have resolved&nbsp;<code>5</code>.</p>\n\n<p>&nbsp;</p>\n<p><strong class=\"example\">Example 1:</strong></p>\n\n<pre>\n<strong>Input:</strong> \ngeneratorFunction = function*() { \n&nbsp; return 42; \n}\ncancelledAt = 100\n<strong>Output:</strong> {&quot;resolved&quot;: 42}\n<strong>Explanation:</strong>\nconst generator = generatorFunction();\nconst [cancel, promise] = cancellable(generator);\nsetTimeout(cancel, 100);\npromise.then(console.log); // resolves 42 at t=0ms\n\nThe generator immediately yields 42 and finishes. Because of that, the returned promise immediately resolves 42. Note that cancelling a finished generator does nothing.\n</pre>\n\n<p><strong class=\"example\">Example 2:</strong></p>\n\n<pre>\n<strong>Input:</strong>\ngeneratorFunction = function*() { \n&nbsp; const msg = yield new Promise(res =&gt; res(&quot;Hello&quot;)); \n&nbsp; throw `Error: ${msg}`; \n}\ncancelledAt = null\n<strong>Output:</strong> {&quot;rejected&quot;: &quot;Error: Hello&quot;}\n<strong>Explanation:</strong>\nA promise is yielded. The function handles this by waiting for it to resolve and then passes the resolved value back to the generator. Then an error is thrown which has the effect of causing the promise to reject with the same thrown error.\n</pre>\n\n<p><strong class=\"example\">Example 3:</strong></p>\n\n<pre>\n<strong>Input:</strong> \ngeneratorFunction = function*() { \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 200)); \n&nbsp; return &quot;Success&quot;; \n}\ncancelledAt = 100\n<strong>Output:</strong> {&quot;rejected&quot;: &quot;Cancelled&quot;}\n<strong>Explanation:</strong>\nWhile the function is waiting for the yielded promise to resolve, cancel() is called. This causes an error message to be sent back to the generator. Since this error is uncaught, the returned promise rejected with this error.\n</pre>\n\n<p><strong class=\"example\">Example 4:</strong></p>\n\n<pre>\n<strong>Input:</strong>\ngeneratorFunction = function*() { \n&nbsp; let result = 0; \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100));\n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100)); \n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; return result;\n}\ncancelledAt = null\n<strong>Output:</strong> {&quot;resolved&quot;: 2}\n<strong>Explanation:</strong>\n4 promises are yielded. Two of those promises have their values added to the result. After 200ms, the generator finishes with a value of 2, and that value is resolved by the returned promise.\n</pre>\n\n<p><strong class=\"example\">Example 5:</strong></p>\n\n<pre>\n<strong>Input:</strong> \ngeneratorFunction = function*() { \n&nbsp; let result = 0; \n&nbsp; try { \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100)); \n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100)); \n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; } catch(e) { \n&nbsp; return result; \n&nbsp; } \n&nbsp; return result; \n}\ncancelledAt = 150\n<strong>Output:</strong> {&quot;resolved&quot;: 1}\n<strong>Explanation:</strong>\nThe first two yielded promises resolve and cause the result to increment. However, at t=150ms, the generator is cancelled. The error sent to the generator is caught and the result is returned and finally resolved by the returned promise.\n</pre>\n\n<p><strong class=\"example\">Example 6:</strong></p>\n\n<pre>\n<strong>Input:</strong> \ngeneratorFunction = function*() { \n&nbsp; try { \n&nbsp; yield new Promise((resolve, reject) =&gt; reject(&quot;Promise Rejected&quot;)); \n&nbsp; } catch(e) { \n&nbsp; let a = yield new Promise(resolve =&gt; resolve(2));\n let b = yield new Promise(resolve =&gt; resolve(2)); \n&nbsp; return a + b; \n&nbsp; }; \n}\ncancelledAt = null\n<strong>Output:</strong> {&quot;resolved&quot;: 4}\n<strong>Explanation:</strong>\nThe first yielded promise immediately rejects. This error is caught. Because the generator hasn&#39;t been cancelled, execution continues as usual. It ends up resolving 2 + 2 = 4.</pre>\n\n<p>&nbsp;</p>\n<p><strong>Constraints:</strong></p>\n\n<ul>\n\t<li><code>cancelledAt == null or 0 &lt;= cancelledAt &lt;= 1000</code></li>\n\t<li><code>generatorFunction</code> returns a generator object</li>\n</ul>\n",
"translatedTitle": "设计可取消函数",
"translatedContent": "<p>有时候你会有一个长时间运行的任务,并且你可能希望在它完成之前取消它。为了实现这个目标,请你编写一个名为 <code>cancellable</code> 的函数,它接收一个生成器对象,并返回一个包含两个值的数组:一个 <strong>取消函数</strong> 和一个 <strong>promise</strong> 对象。</p>\n\n<p>你可以假设生成器函数只会生成 promise 对象。你的函数负责将 promise 对象解析的值传回生成器。如果 promise 被拒绝,你的函数应将该错误抛回给生成器。</p>\n\n<p>如果在生成器完成之前调用了取消回调函数,则你的函数应该将错误抛回给生成器。该错误应该是字符串 <code>\"Cancelled\"</code>(而不是一个 <code>Error</code> 对象)。如果错误被捕获,则返回的 promise 应该解析为下一个生成或返回的值。否则promise 应该被拒绝并抛出该错误。不应执行任何其他代码。</p>\n\n<p>当生成器完成时,您的函数返回的 promise 应该解析为生成器返回的值。但是,如果生成器抛出错误,则返回的 promise 应该拒绝并抛出该错误。</p>\n\n<p>下面的示例展示了你的代码会如何被使用:</p>\n\n<pre>\nfunction* tasks() {\n const val = yield new Promise(resolve =&gt; resolve(2 + 2));\n yield new Promise(resolve =&gt; setTimeout(resolve, 100));\n return val + 1; // calculation shouldn't be done.\n}\nconst [cancel, promise] = cancellable(tasks());\nsetTimeout(cancel, 50);\npromise.catch(console.log); // logs \"Cancelled\" at t=50ms\n</pre>\n\n<p>相反,如果 <code>cancel()</code> 没有被调用或者在 <code>t=100ms</code> 之后才被调用,那么 promise 应被解析为 <code>5</code> 。</p>\n\n<p>&nbsp;</p>\n\n<p><strong>示例 1</strong></p>\n\n<pre>\n<strong>输入:</strong>\ngeneratorFunction = function*() { \n&nbsp; return 42; \n}\ncancelledAt = 100\n<strong>输出:</strong>{\"resolved\": 42}\n<strong>解释:</strong>\nconst generator = generatorFunction();\nconst [cancel, promise] = cancellable(generator);\nsetTimeout(cancel, 100);\npromise.then(console.log); // 在 t=0ms 解析为 42\n\n该生成器立即生成 42 并完成。因此,返回的 promise 立即解析为 42。请注意取消已经完成的生成器没有任何作用。\n</pre>\n\n<p><strong>示例 2</strong></p>\n\n<pre>\n<strong>输入:</strong>\ngeneratorFunction = function*() { \n&nbsp; const msg = yield new Promise(res =&gt; res(\"Hello\")); \n&nbsp; throw `Error: ${msg}`; \n}\ncancelledAt = null\n<strong>输出:</strong>{\"rejected\": \"Error: Hello\"}\n<strong>解释:</strong>\n一个 Promise 被生成。该函数通过等待 promise 解析并将解析后的值传回生成器来处理它。然后抛出一个错误,这会导致 promise 被同样抛出的错误拒绝。\n</pre>\n\n<p><strong>示例 3</strong></p>\n\n<pre>\n<strong>输入:</strong>\ngeneratorFunction = function*() { \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 200)); \n&nbsp; return \"Success\"; \n}\ncancelledAt = 100\n<strong>输出:</strong>{\"rejected\": \"Cancelled\"}\n<strong>解释:</strong>\n当函数等待被生成的 promise 解析时cancel() 被调用。这会导致一个错误消息被发送回生成器。由于这个错误没有被捕获,返回的 promise 会因为这个错误而被拒绝。\n</pre>\n\n<p><strong>示例 4</strong></p>\n\n<pre>\n<strong>输入:</strong>\ngeneratorFunction = function*() { \n&nbsp; let result = 0; \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100));\n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100)); \n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; return result;\n}\ncancelledAt = null\n<strong>输出:</strong>{\"resolved\": 2}\n<strong>解释:</strong>\n生成器生成了 4 个 promise 。其中两个 promise 的值被添加到结果中。200ms 后,生成器以值 2 完成,该值被返回的 promise 解析。\n</pre>\n\n<p><strong>示例 5</strong></p>\n\n<pre>\n<strong>输入:</strong>\ngeneratorFunction = function*() { \n&nbsp; let result = 0; \n&nbsp; try { \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100)); \n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; yield new Promise(res =&gt; setTimeout(res, 100)); \n&nbsp; result += yield new Promise(res =&gt; res(1)); \n&nbsp; } catch(e) { \n&nbsp; return result; \n&nbsp; } \n&nbsp; return result; \n}\ncancelledAt = 150\n<strong>输出:</strong>{\"resolved\": 1}\n<strong>解释:</strong>\n前两个生成的 promise 解析并导致结果递增。然而,在 t=150ms 时,生成器被取消了。发送给生成器的错误被捕获,结果被返回并最终由返回的 promise 解析。\n</pre>\n\n<p><strong>示例 6</strong></p>\n\n<pre>\n<strong>输入:</strong>\ngeneratorFunction = function*() { \n&nbsp; try { \n&nbsp; yield new Promise((resolve, reject) =&gt; reject(\"Promise Rejected\")); \n&nbsp; } catch(e) { \n&nbsp; let a = yield new Promise(resolve =&gt; resolve(2));\n let b = yield new Promise(resolve =&gt; resolve(2)); \n&nbsp; return a + b; \n&nbsp; }; \n}\ncancelledAt = null\n<strong>输出:</strong>{\"resolved\": 4}\n<strong>解释:</strong>\n第一个生成的 promise 立即被拒绝。该错误被捕获。因为生成器没有被取消,执行继续像往常一样。最终解析为 2 + 2 = 4。</pre>\n\n<p>&nbsp;</p>\n\n<p><strong>提示:</strong></p>\n\n<ul>\n\t<li><code>cancelledAt == null or 0 &lt;= cancelledAt &lt;= 1000</code></li>\n\t<li><code>generatorFunction</code> 返回一个生成器对象</li>\n</ul>\n",
"isPaidOnly": false,
"difficulty": "Hard",
"likes": 8,
"dislikes": 0,
"isLiked": null,
"similarQuestions": "[]",
"contributors": [],
"langToValidPlayground": "{\"cpp\": true, \"java\": true, \"python\": true, \"python3\": true, \"mysql\": false, \"mssql\": false, \"oraclesql\": false, \"c\": false, \"csharp\": false, \"javascript\": false, \"typescript\": false, \"bash\": false, \"php\": false, \"swift\": false, \"kotlin\": false, \"dart\": false, \"golang\": false, \"ruby\": false, \"scala\": false, \"html\": false, \"pythonml\": false, \"rust\": false, \"racket\": false, \"erlang\": false, \"elixir\": false, \"pythondata\": false, \"react\": false, \"vanillajs\": false, \"postgresql\": false}",
"topicTags": [],
"companyTagStats": null,
"codeSnippets": [
{
"lang": "JavaScript",
"langSlug": "javascript",
"code": "/**\n * @param {Generator} generator\n * @return {[Function, Promise]}\n */\nvar cancellable = function(generator) {\n \n};\n\n/**\n * function* tasks() {\n * const val = yield new Promise(resolve => resolve(2 + 2));\n * yield new Promise(resolve => setTimeout(resolve, 100));\n * return val + 1;\n * }\n * const [cancel, promise] = cancellable(tasks());\n * setTimeout(cancel, 50);\n * promise.catch(console.log); // logs \"Cancelled\" at t=50ms\n */",
"__typename": "CodeSnippetNode"
},
{
"lang": "TypeScript",
"langSlug": "typescript",
"code": "function cancellable<T>(generator: Generator<Promise<any>, T, unknown>): [() => void, Promise<T>] {\n\t\n};\n\n/**\n * function* tasks() {\n * const val = yield new Promise(resolve => resolve(2 + 2));\n * yield new Promise(resolve => setTimeout(resolve, 100));\n * return val + 1;\n * }\n * const [cancel, promise] = cancellable(tasks());\n * setTimeout(cancel, 50);\n * promise.catch(console.log); // logs \"Cancelled\" at t=50ms\n */",
"__typename": "CodeSnippetNode"
}
],
"stats": "{\"totalAccepted\": \"639\", \"totalSubmission\": \"1.1K\", \"totalAcceptedRaw\": 639, \"totalSubmissionRaw\": 1132, \"acRate\": \"56.4%\"}",
"hints": [
"This question tests understanding of two-way communication between generator functions and the code that evaluates the generator. It is a powerful technique which is used in libraries such as redux-saga.",
"You can pass a value value to a generator function X by calling generator.next(X). Then in the generator function, you can access this value by calling let X = yield \"val to pass into generator.next()\";",
"You can throw an error back to a generator function by calling generator.throw(err). If this error isn't caught in the generator function, that will throw an error."
],
"solution": null,
"status": null,
"sampleTestCase": "function*() { return 42; }\n{\"cancelledAt\":100}",
"metaData": "{\n \"name\": \"cancellable\",\n \"params\": [\n {\n \"name\": \"generator\",\n \"type\": \"string\"\n },\n {\n \"type\": \"integer\",\n \"name\": \"cancelledAT\"\n }\n ],\n \"return\": {\n \"type\": \"string\"\n },\n \"languages\": [\n \"javascript\",\n \"typescript\"\n ],\n \"manual\": true\n}",
"judgerAvailable": true,
"judgeType": "large",
"mysqlSchemas": [],
"enableRunCode": true,
"envInfo": "{\"javascript\":[\"JavaScript\",\"<p>\\u7248\\u672c\\uff1a<code>Node.js 16.13.2<\\/code><\\/p>\\r\\n\\r\\n<p>\\u60a8\\u7684\\u4ee3\\u7801\\u5728\\u6267\\u884c\\u65f6\\u5c06\\u5e26\\u4e0a <code>--harmony<\\/code> \\u6807\\u8bb0\\u6765\\u5f00\\u542f <a href=\\\"http:\\/\\/node.green\\/\\\" target=\\\"_blank\\\">\\u65b0\\u7248ES6\\u7279\\u6027<\\/a>\\u3002<\\/p>\\r\\n\\r\\n<p><a href=\\\"https:\\/\\/lodash.com\\\" target=\\\"_blank\\\">lodash.js<\\/a> \\u5e93\\u5df2\\u7ecf\\u9ed8\\u8ba4\\u88ab\\u5305\\u542b\\u3002<\\/p>\\r\\n\\r\\n<p> \\u5982\\u9700\\u4f7f\\u7528\\u961f\\u5217\\/\\u4f18\\u5148\\u961f\\u5217\\uff0c\\u60a8\\u53ef\\u4f7f\\u7528 <a href=\\\"https:\\/\\/github.com\\/datastructures-js\\/priority-queue\\/tree\\/fb4fdb984834421279aeb081df7af624d17c2a03\\\" target=\\\"_blank\\\"> datastructures-js\\/priority-queue@5.3.0<\\/a> \\u548c <a href=\\\"https:\\/\\/github.com\\/datastructures-js\\/queue\\/tree\\/e63563025a5a805aa16928cb53bcd517bfea9230\\\" target=\\\"_blank\\\"> datastructures-js\\/queue@4.2.1<\\/a>\\u3002<\\/p>\"],\"typescript\":[\"TypeScript\",\"<p>TypeScript 5.1.6<\\/p>\\r\\n\\r\\n<p>Compile Options: --alwaysStrict --strictBindCallApply --strictFunctionTypes --target ES2022<\\/p>\\r\\n\\r\\n<p><a href=\\\"https:\\/\\/lodash.com\\\" target=\\\"_blank\\\">lodash.js<\\/a> \\u5e93\\u5df2\\u7ecf\\u9ed8\\u8ba4\\u88ab\\u5305\\u542b\\u3002<\\/p>\\r\\n\\r\\n<p> \\u5982\\u9700\\u4f7f\\u7528\\u961f\\u5217\\/\\u4f18\\u5148\\u961f\\u5217\\uff0c\\u60a8\\u53ef\\u4f7f\\u7528 <a href=\\\"https:\\/\\/github.com\\/datastructures-js\\/priority-queue\\/tree\\/fb4fdb984834421279aeb081df7af624d17c2a03\\\" target=\\\"_blank\\\"> datastructures-js\\/priority-queue@5.3.0<\\/a> \\u548c <a href=\\\"https:\\/\\/github.com\\/datastructures-js\\/queue\\/tree\\/e63563025a5a805aa16928cb53bcd517bfea9230\\\" target=\\\"_blank\\\"> datastructures-js\\/queue@4.2.1<\\/a>\\u3002<\\/p>\"]}",
"book": null,
"isSubscribed": false,
"isDailyQuestion": false,
"dailyRecordStatus": null,
"editorType": "CKEDITOR",
"ugcQuestionId": null,
"style": "LEETCODE",
"exampleTestcases": "function*() { return 42; }\n{\"cancelledAt\":100}\nfunction*() { const msg = yield new Promise(res => res(\"Hello\")); throw `Error: ${msg}`; }\n{\"cancelledAt\":null}\nfunction*() { yield new Promise(res => setTimeout(res, 200)); return \"Success\"; }\n{\"cancelledAt\":100}\nfunction*() { let result = 0; yield new Promise(res => setTimeout(res, 100)); result += yield new Promise(res => res(1)); yield new Promise(res => setTimeout(res, 100)); result += yield new Promise(res => res(1)); return result; }\n{\"cancelledAt\":null}\nfunction*() { let result = 0; try { yield new Promise(res => setTimeout(res, 100)); result += yield new Promise(res => res(1)); yield new Promise(res => setTimeout(res, 100)); result += yield new Promise(res => res(1)); } catch(e) { return result; } return result; }\n{\"cancelledAt\":150}\nfunction*() { try { yield new Promise((resolve, reject) => reject(\"Promise Rejected\")); } catch(e) { let a = yield new Promise(resolve => resolve(2)); let b = yield new Promise(resolve => resolve(2)); return a + b; }; }\n{\"cancelledAt\":null}",
"__typename": "QuestionNode"
}
}
}