Nodejs Promise 杂谈
Nodejs Promise 杂谈
本篇文章是笔者近期对 Promise 的几点思考的总结。
https://github.com/zhangxiang958/zhangxiang958.github.io/issues/42
> 有利于消除副作用
不清楚你是怎么理解副作用的,不知能不能给个例子。
callback 和 thenable 的设计,都是构造一个函数传进去,为啥会有不一样。
为啥一个 callback 会关心代码是同步调用还是异步调用。
想不明白有什么场景下会有这种问题。
我觉得楼主的附录里还应该包含一个 You don’t know javascript 的 Promise 章节。。
我在文章中有注明副作用的意思:函数副作用指调用函数时除了返回函数值还会修改函数外的变量 https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%89%AF%E4%BD%9C%E7%94%A8
(在文章中第一个例子就说明了这个问题,也就是通过一个外部变量来控制调用次数,而这个变量是其他代码逻辑无需关心的)
我在文章中说明了 callback 的一些在编码中的危害,而这部分在 you don’t know 中也有提及,很多模型设计都是基于 callback 的,而不同模型的特点就在于它们用于解决什么问题,thenable 或者说 Promise 是用于解决回调中调用时机,调用次数的种种问题的。
文章中的意思并不是使用 callback 需要关心函数内部是同步或者是异步的,而是希望通过 Promise 这个机制来尽可能写出易于掌控的代码。而对于 callback 的调用时机不确定是同步还是异步或者是都有可能的情况下,会引发 release zalgo 问题,而这个问题是 API 设计哲学,也就是设计 API 要么完全同步,要么完全异步,详细可以看 https://blog.izs.me/2013/08/designing-apis-for-asynchrony 或者 https://oren.github.io/blog/zalgo.html
不知道以上能不能解决你的疑问
已添加,感谢提醒
关于副作用,这里我不明白的是,thenable 和传统 callback 的设计,你都要创建一个函数,通常来说,这两种写法,你创建函数的地方都一样,这就意味着,他们的词法作用域是一样的,这种时候,这两种写法,可以修改的 外部 变量是一样的,那这里的。有助于消除副作用 指的是什么。
关于一个 api 有的时候是异步调用 callback 有的时候是同步这个,我不明白,一个 callback 在什么时候需要关心这个。
不知道我的问题描述清楚了没有,我想要的是这两种问题对应的举例。
关于 callback 是同步异步调用的, 因为楼主看了 You don’t know JavaScript 总结的(非贬义), 这部分也基本上和原书的意思差不多
大意是很多人看着 callback 的 API, 就想当然认为这个 callback 会是异步执行, 于是自己的代码依赖于这样的执行顺序, 导致可能结果不符合预期. 但其实我们都知道, 一个 callback 是同步调用还是异步调用从 API 的签名是看不出来的, 即我们不能信任 /依赖 API 对 callback 的执行顺序, 这个执行顺序是不可靠的, 也是书中提到信任问题的一部分.
书中认为这一问题的原因是各个库之间对于 callback 的调用时机缺乏统一的规范, 而 Promise 作为一个规范提供了信任背书, 明确赋予了 callback 异步调用的含义, 所以解决了之前的信任问题, 让人可以看到 API 的签名返回 Promise 就可以确定这是异步 API.
大概是这么个意思…但是可能因为楼主是总结精简了一些, 导致理解产生了一些偏差…
个人觉得,Promise, async/await 这些个玩意,其实就是个语法糖,是 JS 标准制定者为了让程序员写代码的时候,少费心少出错,异步回调写起来、看上去和从上到下的代码执行顺序达到某种“一致”罢了。
这玩意根本就不是啥高深技术,就是 callback 的语法糖,结果各种写博客的、写教程的,搞来搞去,写来写去,分析来分析去,抄来抄去,把简单的玩意搞得复杂无比。
如果是纯 JS 新手,看了这些中文教程,别说看明白了,估计想死。
不是 callback 和 Promise 的本质上的区别,因为 callback 其实也是可以逐级传递错误信息的,只不过用 callback 可以有多重实现方案,而 Promise 只提供了唯一方案,相比来说 Promise 更标准更可靠,标准化的好处就是任何第三方包都遵循标准任何人都可以直接使用,不会遇到错误传递方案不同导致的不兼容。
楼主举得例子不恰,外部放置临时变量来记录异步操作是否成功的方法只是 callback 传递状态的众多办法中的一种,而且可能是最差且几乎没人用的方法,这个方法有很多弊端,比如作用域混乱导致的调试成本太高,以及可读性差等,callback 在调用链上做状态传递是能规避这个方案带来的很多问题的,比较起来,Promise 是用统一标准方案来实现的状态传递,兼容性有保障。
我能懂他看书了,就像我上面写的,什么场景下,一个函数(一个回调 thenable 的或是传统的)需要关心自己是同步被调用还是异步被调用,我这里不明白。
我也觉得就是个糖,咋还就不一样了
callback 你也可以写成 fp 的,用到的东西都通过参数传入。这里说的,thenable 更可靠,这个可靠性是怎么体现的呢?
作用域混乱的问题,和我上面写的一样,你都是要定义函数的,这个时候,(两种写法 改写前 改写后)通常都是一个词法作用域,还能一个混乱一个不混乱的吗?
可靠性的体现是:
//Promise 的错误捕捉机制
(new Promise((res, rej) => {
haha;//这里因为找不到"haha"是什么,所以会报一个错
}
)).catch((error) => {
console.log(‘捕捉到错误了’);
return [];
}).then((result)=>{
console.log(得到的数组的长度为${result.length}
);
});
//一般值传递回调函数的错误捕捉机制
function cb1(cb) {
let result;
try {
result = Math.random();//这里假设做了某种操作,我用 Math.random()来举例
} catch (error) {
cb(error);
}
haha;//这里因为找不到"haha"是什么,所以会报一个错
cb(null, result);
}
function cb2(error, result) {
if (error) {
console.error(error);
} else {
console.log(result);
}
}
cb1(cb2);
Promise 内部有任何问题,包括语法问题,都可以通过后边挂载的 catch 回调捕捉到;但 callback 不一样,得自己在自己觉得可能会有问题的地方加 try/catch,如果 try 没有覆盖到出错误的点,就可能会造成整个程序崩溃。相比来说,因为 Promise 的错误捕捉机制用起来更加简洁、覆盖面更广,所以反映在规模性的项目上可能可靠性更好。你也可以抬杠说在回调函数里每次都全局 try,但相比 Promise 来说要多写一点代码吧,而且看起来也不如 Promise 那么言简意赅。
作用域混乱我是特指的是楼主博客里的一个用法:
let urls = {};
function rpc (url, callback) {
if (urls[url]) {
return callback(urls[url]);
}
request(url, (err, res) => {
urls[url] = res;
callback(err, res);
});
}
urls[‘domain.com’]=3;
oo();//这里可能是手误修改了 urls
rpc(‘domain.com’,console.log);
function oo() {
urls[‘domain.com’] = 2;
}
这里 urls 暴露在一个相对宽广的的作用域下,如果在调用 rpc 之前还有其他的代码执行,则有可能会有意无意地修改 urls 的值,有意的还好,无意的就会产生逻辑 BUG 了。
以上都是针对一些实际情况举的例子,不是为了说明 Promise 一定就比 callback 好用,而是说明 Promise 能解决一些以往使用 callback 的痛点,实际上 Promise、Generator、Async/Await 都是语法糖,都是可以用 callback 实现的(要不然 babel 就不存在了),应用语法糖在特定情况下用可以事半功倍,但如果有的地方你觉得用 callback 最合适,也没必要非要赶着上 Promise。
第一个错误捕获的问题,对等的写法应该是
https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBDADgSwBQXTTaAZwEoA6GACwWzFQBt6AjVMAa3LpjEuzgCd+AYQbNWHADpQpMfgE8ABMCkKFGHHgLEANArqMW7ElIC+e1N0oK+giPxJKVCgcNGG2N-neNQTUkNogmoRQRMigqLYA7gAKkQhhKKgAbhCYACYBIEz84nAwAMrorJhQAObIsgCucIGUMAC29ADqlJjwRMVgcAUJ7ZjJ7XLI4ERhgaVEAjAxuWUNqMgAZgxTgQBWRAAeAEK57PkFqA1wADKlcMurtSCbWwWlZfRwAIpVEPBX9GsgxfxT-BGLCYcno0Cy6H4pRgzQyVGQAA4AAyBSEQKbNXLoEaQuAA5KXQL8OAARyqmGJs1Q80WSBW3xuUwamEq-BqgSIj2ebw+lzp10CMFQTFh6XhSAATILcph6I8hBAGgsRlBoISQFUpgAVYWJelTEwmIA
第二个,thenable 也一样的
https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAbOMAEBXATqgZ0wF5NgBfAbgB0paAzbKMGAS2k1wAcwAKPVAEoytTJwx4omKHADumAAq4IAW1YE4vXHAIRUANzgkAfCKljMrepn74CAbQEBdYcFEWx23Qc0CHzwRpzMXJMOEIjN2CLbQBHbB0YW1QAGnEiYlMojw8-R3wnEnSgnJidPUNk-wLA9w9yWujydwag5rooPOoQABNVAENWKAA6SBVuwtIAZiDaCAheQMwAemXATfjAGcTAe+VAX4DAejNAaSNAe+jAO-lATlNAMLkcO1pabj5uvpVBkbHuwWGYAAsEXkgobxwYaoCAAc0aDCYLHYUnmizMYi6vQGQ1GqgmRQATG1aCAUiAIFw2NACMhQP1cMpZAoKQhSSh+voIKweniQAAjXD9MAAawwAGUuNyhqDkDBcAl8V8YCpUAB1L6seAEIVgOD8ulK1j6JUAT2Q4AIpPxQw0uBgSn6oOeyHo-Qi+IAVgQAB4AIS5vIF-RUcAAMkM4Lb7Rona7+SL0ABFbAQeDBh0gIW4M0G9n9dm6kFQNlcXBDGBylnfZAADgADPi8xANHKuVwDXmdHBcIY2XFsKxtJbrf0E6GQBo1GKJXB8QRI3AY3Gg0g7YmYBmiz0S0hMfjxYNUCKAMKqG0oKDQIP47AaAAqGfp8405HIQA
不知这两个例子我描述清楚了没有
而且 babel 不处理 Promise 的
我看不大懂你写的。。。
你的第一个代码,api 函数执行如果是异步过程的话,你的 try 永远捕捉不到任东西。
第二个的问题不是在于 rpc 是不是用 Promise 实现,而是 urls 暴漏在那么广的作用域下会有风险(让然代码逻辑处理好也是可以没问题的),把 urls 封装在 Promise 里,通过每一级的.then 向下传播,中间不可能被外界作用域的程序篡改:<br>let urls = {};<br><br>let theP = new Promise((res, rej) => {<br> let urls = {};<br> res(urls);<br>}).then(urls => {<br> urls["domain 点 com"] = 3;<br> return urls;<br>});<br>urls["domain 点 com"] = 2;<br>theP.then(urls => {<br> console.log(urls["domain 点 com"]);<br>});<br>
这里边有两个 urls,但是 Promise 能保证调用链中传递的 urls 不受外部作用域的 urls 的操作影响。
上面说的都不是绝对的,得看实际情况。
bable 可以根据需求转换语法,最著名的是把 ES6+转换成纯 ES5,最早 core.js 部分就包含 Promise 的 polyfill 了,只不过现在很多人不需要那么苛刻要求转成 ES5。
去看一下 /polyfill 文档吧。
这里说 babel 只当它是 transcompiler。
try 捕获不到东西 emmmmm,可能是我学的不是 js
#9 里面我也说了
callback 你也可以写成 fp 的,用到的东西都通过参数传入
这些都不是 thenable 的优点(指楼主文中提到的部分)。
希望举例子的时候能用点心。
你开心就好
在Node.js中,Promise 是一种处理异步操作的重要机制。它提供了一种优雅的方式来管理异步任务的完成(resolve)或失败(reject)。以下是对 Node.js 中 Promise 的一些基本杂谈和示例代码。
基本用法
Promise 的基本结构如下:
let promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value); // 将 promise 状态改为 fulfilled
} else {
reject(error); // 将 promise 状态改为 rejected
}
});
promise.then(
value => {
// 处理成功的结果
},
error => {
// 处理失败的结果
}
);
链式调用
Promise 支持链式调用,这使得处理多个异步任务变得非常方便:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`最终结果: ${finalResult}`);
})
.catch(failureCallback);
Promise.all 和 Promise.race
Promise.all
用于并行执行多个 Promise,并在所有 Promise 都成功时返回结果:
Promise.all([promise1, promise2, promise3]).then(values => {
console.log(values);
});
Promise.race
则会在第一个完成的 Promise 完成后立即返回其结果:
Promise.race([promise1, promise2, promise3]).then(value => {
console.log(value);
});
总之,Promise 是 Node.js 中处理异步编程的强大工具,熟练掌握 Promise 可以极大地提高代码的可读性和维护性。