NodejsLearning
使用 Node.js 的同学, 一定免不了使用 Promise
, 说到 Promise
, 忍不住想要吹一锅 Node.js 了. Promise
配合 async
和 await
, 同步异步随时切换, 完全告别了异步时回调嵌套产生的 ugly code.
我们假设这样一个需求, 一段业务逻辑,连续几个异步操作,非常常见的需求.
-
先看下最丑陋的搞法, 一堆回掉嵌套, 找打那种的
(async function () { let data = 'happy_coding'; setTimeout(() => { try { data = `step[1]_${data}`; console.log(data); } catch (error) { console.log('error = ', error); } setTimeout(() => { try { data = `step[2]_${data}`; console.log(data); } catch (error) { console.log('error = ', error); } setTimeout(() => { try { data = `step[3]_${data}`; console.log(data); } catch (error) { console.log('error = ', error); } }, 1000); }, 1000); }, 1000); })();
-
再看下稍好点的情况
function execAsync(sth) { return new Promise((resolve, reject) => { setTimeout(() => resolve(sth), 1000); }) } (async function () { execAsync('happy_coding') .then((data) => { let tmp = `step[1]_${data}`; console.log(tmp); return execAsync(tmp); }) .then((data) => { let tmp = `step[2]_${data}`; console.log(tmp); return execAsync(tmp); }) .then((data) => { let tmp = `step[3]_${data}`; console.log(tmp); return execAsync(tmp); }) .catch((error) => { console.log('error = ', error); }); })();
把异步操作都装进 Promise
, 然后 then
...then
...catch
就好, 当然, 对于非 js 研发同学, 上面的代码已经很漂亮了. 小夫当年写 Java 的时候, 突然有天出来个 RxJava, 就是上面这样的, 当时写起来那酸爽劲, 现在想起来还有些感动, 然后, 带着这份感动直至遇见 Node.js.
-
同步方式写异步
function execAsync(sth) { return new Promise((resolve, reject) => { setTimeout(() => resolve(sth), 1000); }) } (async function () { try { let data = await execAsync('happy_coding'); data = `step[1]_${data}`; console.log(data); data = await execAsync(data); data = `step[2]_${data}`; console.log(data); data = await execAsync(data); data = `step[3]_${data}`; console.log(data); } catch (error) { console.log('error = ', error); } })();
以上, 把异步操作都装进 Promise
后, 如果想要同步调用, 那么前面 await
一下就好了, 如果不想让这个异步操作阻塞主流程, 那么去掉 await
就好了. 美不美, 简直美. 这可是面向未来的语法. 哈哈. 我知道的好像 Dart 里面也这样搞, Python 里引用 asyncio
也可以, 但是不向 Node.js 里面这么直接.
好了, 前面只是替 Node.js 吹个牛逼, 下面说正题了.
Node.js 中虽然提供了 Promise
, 但是有的业务里不够用. 比如最近我的一个需求中就需要一个带超时时间的 Promise
, 怎么搞嘞, 查了些许资料, 然后自己封装了下:
class TimeoutPromise extends Promise {
constructor(callback, ms = 30 * 1000) {
let timeout;
let wrapperPromise = Promise.race([
new Promise(callback),
new Promise((resolve, reject) => {
timeout = setTimeout(() => {
reject(new Error('PTIMEOUT'));
}, ms);
}),
]);
super((resolve, reject) => {
wrapperPromise.then((data) => {
clearTimeout(timeout);
resolve(data);
}).catch((error) => {
clearTimeout(timeout);
reject(error); // if timeout, reject the `PTIMEOUT` error
})
});
}
}
我们做下测试:
(async function () {
let start = Date.now();
function test() {
return new TimeoutPromise((resolve, reject) => {
setTimeout(() => { // 模拟异步耗时操作
resolve('finished');
}, 5000); // 异步任务超时时间设置为 5s
}, 3000) // Promise 超时时间设置为 3s
}
try {
await test(); // 该函数会在 3s 后执行完毕
} catch (error) {
console.log(' --> Error = ', error);
}
console.log('spent: ', (Date.now() - start) / 1000);
})();
下面是执行结果:
--> Error = Error: PTIMEOUT
at Timeout.setTimeout (/home/modorone/WorkSpace/test/timeout_promise/test04.js:18:28)
at ontimeout (timers.js:475:11)
at tryOnTimeout (timers.js:310:5)
at Timer.listOnTimeout (timers.js:270:5)
spent: 3.011
需要说明的是, await test()
会在 3s 后得到返回值, 但是 test()
中的异步操作依然会在自己的 timeout, 即 5s 之后完成.
小夫参考了 here. 里面实际上提到了两种实现, 我觉得 Promise.race()
比较美.