V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
shawncheung
V2EX  ›  Node.js

Promise 杂谈

  •  1
     
  •   shawncheung · 2019-01-27 18:50:17 +08:00 · 4790 次点击
    这是一个创建于 2133 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    15 条回复    2019-01-29 09:47:05 +08:00
    azh7138m
        1
    azh7138m  
       2019-01-27 23:55:35 +08:00 via Android
    > 有利于消除副作用

    不清楚你是怎么理解副作用的,不知能不能给个例子。
    callback 和 thenable 的设计,都是构造一个函数传进去,为啥会有不一样。

    为啥一个 callback 会关心代码是同步调用还是异步调用。
    想不明白有什么场景下会有这种问题。
    Sparetire
        2
    Sparetire  
       2019-01-28 03:04:36 +08:00 via Android
    我觉得楼主的附录里还应该包含一个 You don't know javascript 的 Promise 章节。。
    shawncheung
        3
    shawncheung  
    OP
       2019-01-28 09:47:30 +08:00
    @azh7138m 我在文章中有注明副作用的意思:函数副作用指调用函数时除了返回函数值还会修改函数外的变量 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

    不知道以上能不能解决你的疑问
    shawncheung
        4
    shawncheung  
    OP
       2019-01-28 09:47:46 +08:00
    @Sparetire 已添加,感谢提醒
    azh7138m
        5
    azh7138m  
       2019-01-28 11:03:59 +08:00 via Android
    关于副作用,这里我不明白的是,thenable 和传统 callback 的设计,你都要创建一个函数,通常来说,这两种写法,你创建函数的地方都一样,这就意味着,他们的词法作用域是一样的,这种时候,这两种写法,可以修改的 外部 变量是一样的,那这里的。有助于消除副作用 指的是什么。


    关于一个 api 有的时候是异步调用 callback 有的时候是同步这个,我不明白,一个 callback 在什么时候需要关心这个。

    不知道我的问题描述清楚了没有,我想要的是这两种问题对应的举例。
    Sparetire
        6
    Sparetire  
       2019-01-28 14:38:41 +08:00
    @azh7138m 关于 callback 是同步异步调用的, 因为楼主看了 You don't know JavaScript 总结的(非贬义), 这部分也基本上和原书的意思差不多
    大意是很多人看着 callback 的 API, 就想当然认为这个 callback 会是异步执行, 于是自己的代码依赖于这样的执行顺序, 导致可能结果不符合预期. 但其实我们都知道, 一个 callback 是同步调用还是异步调用从 API 的签名是看不出来的, 即我们不能信任 /依赖 API 对 callback 的执行顺序, 这个执行顺序是不可靠的, 也是书中提到信任问题的一部分.
    书中认为这一问题的原因是各个库之间对于 callback 的调用时机缺乏统一的规范, 而 Promise 作为一个规范提供了信任背书, 明确赋予了 callback 异步调用的含义, 所以解决了之前的信任问题, 让人可以看到 API 的签名返回 Promise 就可以确定这是异步 API.
    大概是这么个意思...但是可能因为楼主是总结精简了一些, 导致理解产生了一些偏差...
    hoyixi
        7
    hoyixi  
       2019-01-28 15:53:05 +08:00
    个人觉得,Promise, async/await 这些个玩意,其实就是个语法糖,是 JS 标准制定者为了让程序员写代码的时候,少费心少出错,异步回调写起来、看上去和从上到下的代码执行顺序达到某种“一致”罢了。

    这玩意根本就不是啥高深技术,就是 callback 的语法糖,结果各种写博客的、写教程的,搞来搞去,写来写去,分析来分析去,抄来抄去,把简单的玩意搞得复杂无比。

    如果是纯 JS 新手,看了这些中文教程,别说看明白了,估计想死。
    libook
        8
    libook  
       2019-01-28 16:02:19 +08:00
    @azh7138m 不是 callback 和 Promise 的本质上的区别,因为 callback 其实也是可以逐级传递错误信息的,只不过用 callback 可以有多重实现方案,而 Promise 只提供了唯一方案,相比来说 Promise 更标准更可靠,标准化的好处就是任何第三方包都遵循标准任何人都可以直接使用,不会遇到错误传递方案不同导致的不兼容。

    楼主举得例子不恰,外部放置临时变量来记录异步操作是否成功的方法只是 callback 传递状态的众多办法中的一种,而且可能是最差且几乎没人用的方法,这个方法有很多弊端,比如作用域混乱导致的调试成本太高,以及可读性差等,callback 在调用链上做状态传递是能规避这个方案带来的很多问题的,比较起来,Promise 是用统一标准方案来实现的状态传递,兼容性有保障。
    azh7138m
        9
    azh7138m  
       2019-01-28 16:32:57 +08:00 via Android
    @Sparetire 我能懂他看书了,就像我上面写的,什么场景下,一个函数(一个回调 thenable 的或是传统的)需要关心自己是同步被调用还是异步被调用,我这里不明白。


    @hoyixi 我也觉得就是个糖,咋还就不一样了


    @libook callback 你也可以写成 fp 的,用到的东西都通过参数传入。这里说的,thenable 更可靠,这个可靠性是怎么体现的呢?
    作用域混乱的问题,和我上面写的一样,你都是要定义函数的,这个时候,(两种写法 改写前 改写后)通常都是一个词法作用域,还能一个混乱一个不混乱的吗?
    libook
        10
    libook  
       2019-01-28 19:05:13 +08:00
    @azh7138m 可靠性的体现是:

    //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。
    azh7138m
        12
    azh7138m  
       2019-01-28 21:06:08 +08:00
    @libook 而且 babel 不处理 Promise 的
    libook
        13
    libook  
       2019-01-29 00:16:22 +08:00
    @azh7138m

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

    ```
    let urls = {};

    let theP = new Promise((res, rej) => {
    let urls = {};
    res(urls);
    }).then(urls => {
    urls["domain 点 com"] = 3;
    return urls;
    });
    urls["domain 点 com"] = 2;
    theP.then(urls => {
    console.log(urls["domain 点 com"]);
    });
    ```

    这里边有两个 urls,但是 Promise 能保证调用链中传递的 urls 不受外部作用域的 urls 的操作影响。

    上面说的都不是绝对的,得看实际情况。

    bable 可以根据需求转换语法,最著名的是把 ES6+转换成纯 ES5,最早 core.js 部分就包含 Promise 的 polyfill 了,只不过现在很多人不需要那么苛刻要求转成 ES5。
    去看一下 @babel/polyfill 文档吧。
    azh7138m
        14
    azh7138m  
       2019-01-29 02:31:50 +08:00 via Android
    @libook 这里说 babel 只当它是 transcompiler。
    try 捕获不到东西 emmmmm,可能是我学的不是 js

    #9 里面我也说了
    callback 你也可以写成 fp 的,用到的东西都通过参数传入

    这些都不是 thenable 的优点(指楼主文中提到的部分)。
    希望举例子的时候能用点心。
    libook
        15
    libook  
       2019-01-29 09:47:05 +08:00 via Android
    @azh7138m 你开心就好
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2688 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 05:27 · PVG 13:27 · LAX 21:27 · JFK 00:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.