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

Promise 杂谈

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

    本篇文章是笔者近期对 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     Solana     5233 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 03:43 PVG 11:43 LAX 19:43 JFK 22:43
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86