本机JavaScriptpromise 没有任何超时机制.
关于您的实现的问题可能更适合http://codereview.stackexchange.com人,但有几点需要注意:
你没有提供在promise 中实际做任何事情的方法
setTimeout
回调中不需要clearTimeout
,因为setTimeout
会安排一个一次性计时器.
既然promise 一旦被解决/拒绝就不能被解决/拒绝,你就不需要那张支票了.
继续你的myPromise
函数方法,也许是这样的:
function myPromise(timeout, callback) {
return new Promise((resolve, reject) => {
// Set up the timeout
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout} ms`));
}, timeout);
// Set up the real work
callback(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error) => {
clearTimeout(timer);
reject(error);
}
);
});
}
用法如下:
myPromise(2000, (resolve, reject) => {
// Real work is here
});
(或者可能稍微不那么复杂,请参见下面的分隔符.)
我有点担心语义是不同的(不是new
,而是Promise
构造函数使用new
).但更大的问题是,它假设你总是从头开始创建promise ,但你通常希望能够使用你已经拥有的promise .
您可以通过将Promise
子类化来处理这两个问题:
class MyPromise extends Promise {
constructor(timeout, callback) {
// We need to support being called with no milliseconds
// value, because the various Promise methods (`then` and
// such) correctly call the subclass constructor when
// building the new promises they return.
const haveTimeout = typeof timeout === "number";
const init = haveTimeout ? callback : timeout;
super((resolve, reject) => {
if (haveTimeout) {
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout}ms`));
}, timeout);
init(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error) => {
clearTimeout(timer);
reject(error);
}
);
} else {
init(resolve, reject);
}
});
}
// Pick your own name of course. (You could even override `resolve` itself
// if you liked; just be sure to do the same arguments detection we do
// above in the constructor, since you need to support the standard use of
// `resolve`.)
static resolveWithTimeout(timeout, x) {
if (!x || typeof x.then !== "function") {
// `x` isn't a thenable, no need for the timeout,
// fulfill immediately
return this.resolve(x);
}
return new this(timeout, x.then.bind(x));
}
}
用法(如果构建新promise ):
let p = new MyPromise(300, (resolve, reject) => {
// ...
});
p.then((value) => {
// ...
})
.catch((error) => {
// ...
});
用法(如果使用您已有的promise ):
MyPromise.resolveWithTimeout(100, somePromiseYouAlreadyHave)
.then((value) => {
// ...
})
.catch((error) => {
// ...
});
Live Example:
"use strict";
class MyPromise extends Promise {
constructor(timeout, callback) {
// We need to support being called with no milliseconds
// value, because the various Promise methods (`then` and
// such) correctly call the subclass constructor when
// building the new promises they return.
const haveTimeout = typeof timeout === "number";
const init = haveTimeout ? callback : timeout;
super((resolve, reject) => {
if (haveTimeout) {
const timer = setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout}ms`));
}, timeout);
init(
(value) => {
clearTimeout(timer);
resolve(value);
},
(error) => {
clearTimeout(timer);
reject(error);
}
);
} else {
init(resolve, reject);
}
});
}
// Pick your own name of course. (You could even override `resolve` itself
// if you liked; just be sure to do the same arguments detection we do
// above in the constructor, since you need to support the standard use of
// `resolve`.)
static resolveWithTimeout(timeout, x) {
if (!x || typeof x.then !== "function") {
// `x` isn't a thenable, no need for the timeout,
// fulfill immediately
return this.resolve(x);
}
return new this(timeout, x.then.bind(x));
}
}
// Some functions for the demonstration
const neverSettle = () => new Promise(() => {});
const fulfillAfterDelay = (delay, value) => new Promise((resolve) => setTimeout(resolve, delay, value));
const rejectAfterDelay = (delay, error) => new Promise((resolve, reject) => setTimeout(reject, delay, error));
const examples = [
function usageWhenCreatingNewPromise1() {
console.log("Showing timeout when creating new promise");
const p = new MyPromise(100, (resolve, reject) => {
// We never resolve/reject, so we test the timeout
});
return p.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},
function usageWhenCreatingNewPromise2() {
console.log("Showing when the promise is fulfilled before the timeout");
const p = new MyPromise(100, (resolve, reject) => {
setTimeout(resolve, 50, "worked");
});
return p.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},
function usageWhenCreatingNewPromise3() {
console.log("Showing when the promise is rejected before the timeout");
const p = new MyPromise(100, (resolve, reject) => {
setTimeout(reject, 50, new Error("failed"));
});
return p.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},
function usageWhenYouAlreadyHaveAPromise1() {
console.log("Showing timeout when using a promise we already have");
return MyPromise.resolveWithTimeout(100, neverSettle())
.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},
function usageWhenYouAlreadyHaveAPromise2() {
console.log("Showing fulfillment when using a promise we already have");
return MyPromise.resolveWithTimeout(100, fulfillAfterDelay(50, "worked"))
.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},
function usageWhenYouAlreadyHaveAPromise3() {
console.log("Showing rejection when using a promise we already have");
return MyPromise.resolveWithTimeout(100, rejectAfterDelay(50, new Error("failed")))
.then((value) => {
console.log(`Fulfilled: ${value}`);
})
.catch((error) => {
console.log(`Rejected: ${error}`);
});
},
async function usageInAnAsyncFunction1() {
console.log("Showing timeout in async function");
try {
const value = await MyPromise.resolveWithTimeout(100, neverSettle());
console.log(`Fulfilled: ${value}`);
} catch (error) {
console.log(`Rejected: ${error}`);
}
},
async function usageInAnAsyncFunction2() {
console.log("Showing fulfillment in async function");
try {
const value = await MyPromise.resolveWithTimeout(100, fulfillAfterDelay(50, "worked"));
console.log(`Fulfilled: ${value}`);
} catch (error) {
console.log(`Rejected: ${error}`);
}
},
async function usageInAnAsyncFunction3() {
console.log("Showing rejection in async function");
try {
const value = await MyPromise.resolveWithTimeout(100, rejectAfterDelay(50, new Error("failed")));
console.log(`Fulfilled: ${value}`);
} catch (error) {
console.log(`Rejected: ${error}`);
}
},
];
(async () => {
for (const example of examples) {
try {
await example();
} catch (e) {
}
}
})();
/* Shows the cosole full height in the snippet */
.as-console-wrapper {
max-height: 100% !important;
}
上述代码在解决或拒绝promise 时主动取消计时器.这可能不是必需的,这取决于您的用例,并使代码有点复杂.这不是promise 的必要部分;一旦promise 被解决或拒绝,且无法更改,再次调用resolve
或reject
函数对promise 没有影响(the spec对此很清楚).但如果不取消计时器,计时器仍处于挂起状态,直到它触发.一个悬而未决的promise 将保留 node .例如,如果你在做最后一件事时有很长的超时时间,就会毫无意义地延迟退出进程.浏览器不会延迟使用挂起的计时器离开页面,因此这不适用于浏览器.因此,你的里程可能会有所不同,你可以通过不取消计时器来简化一些.
如果您不关心挂起的计时器,MyPromise
将是更简单的:
class MyPromise extends Promise {
constructor(timeout, callback) {
// We need to support being called with no milliseconds
// value, because the various Promise methods (`then` and
// such) correctly call the subclass constructor when
// building the new promises they return.
const haveTimeout = typeof timeout === "number";
const init = haveTimeout ? callback : timeout;
super((resolve, reject) => {
init(resolve, reject);
if (haveTimeout) {
setTimeout(() => {
reject(new Error(`Promise timed out after ${timeout}ms`));
}, timeout);
}
});
}
// Pick your own name of course. (You could even override `resolve` itself
// if you liked; just be sure to do the same arguments detection we do
// above in the constructor, since you need to support the standard use of
// `resolve`.)
static resolveWithTimeout(timeout, x) {
if (!x || typeof x.then !== "function") {
// `x` isn't a thenable, no need for the timeout,
// fulfill immediately
return this.resolve(x);
}
return new this(timeout, x.then.bind(x));
}
}