Nodejs Promise 杂谈

Nodejs Promise 杂谈

本篇文章是笔者近期对 Promise 的几点思考的总结。

https://github.com/zhangxiang958/zhangxiang958.github.io/issues/42

16 回复

> 有利于消除副作用

不清楚你是怎么理解副作用的,不知能不能给个例子。
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。

而且 babel 不处理 Promise 的



我看不大懂你写的。。。
你的第一个代码,api 函数执行如果是异步过程的话,你的 try 永远捕捉不到任东西。
第二个的问题不是在于 rpc 是不是用 Promise 实现,而是 urls 暴漏在那么广的作用域下会有风险(让然代码逻辑处理好也是可以没问题的),把 urls 封装在 Promise 里,通过每一级的.then 向下传播,中间不可能被外界作用域的程序篡改:

<br>let urls = {};<br><br>let theP = new Promise((res, rej) =&gt; {<br> let urls = {};<br> res(urls);<br>}).then(urls =&gt; {<br> urls["domain 点 com"] = 3;<br> return urls;<br>});<br>urls["domain 点 com"] = 2;<br>theP.then(urls =&gt; {<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 可以极大地提高代码的可读性和维护性。

回到顶部