上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
12下一页
返回列表 发新帖

[油猴脚本开发指南番外篇]为什么我们需要Promise

[复制链接]
  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-2-16 20:38:10 | 显示全部楼层 | 阅读模式

    [md]# 前言
    之前虽然也介绍过promise
    但是只是告诉promise的用法和特性
    c大推荐聊一下为什么出现promise
    所以就决定开这一篇番外的短篇了
    因为我个人是半路出身的,所以对js的理解并不是特别深入
    可能存在事实性错误
    欢迎评论指出~

    正文

    假设我们有一个函数,这个函数存在一些异步的操作

    function sleep(){
        setTimeout(() => {
            console.log('我执行完毕了')
        }, 3000);
    }

    我们想要执行sleep休眠到3s后再执行下一个函数
    这个时候如果直接

    sleep(3000)
    next()

    是行不通的
    那怎么办?
    因为JS里函数是一等公民(可以作为函数参数,可以作为函数返回值,也可以赋值给变量。)
    所以我们可以把函数传递给sleep函数,让他在执行3s后再执行传入的函数

    function sleep(callback){
        setTimeout(() => {
            callback()
        }, 3000);
    }
    console.log('我执行了')
    sleep(()=>{
        console.log('我等待了3s')
    })

    到这里一切都岁月静好,但是假设我们的层级多了

    function sleep(callback) {
      setTimeout(() => {
        callback();
      }, 3000);
    }
    console.log("我执行了");
    sleep(() => {
      console.log("我等待了3s");
      sleep(() => {
        console.log("我等待了6s");
        sleep(() => {
          console.log("我等待了9s");
          sleep(() => {
            console.log("我等待了12s");
          });
        });
      });
    });

    就出现了回调地狱,代码的长度并没有增加,而全部曲折在了一个函数内不断增加宽度。
    (补充,当然我们也可以把sleep函数给拆开,分别执行等待3,6,9,12s,这里是将同样的sleep视为不同的step1)
    这种代码也被戏称为厄运金字塔
    ![image](https://bbs.tampermonkey.net.cn/ ... o5596azhh0zz9ac.png)
    但是如果我们抽象一下逻辑,发现虽然是回调地狱,实际执行流程是线性的
    A-》B-》C-》D
    所以我们可以创造一个对象,这个对象来负责管理异步函数,当异步函数执行完毕后执行回调,回调触发这个对象的下一步操作
    伪代码大概是这样的

    wrapfunc((callback)=>{
        callback(1)
    }).next((ret)=>{
        console.log(ret)//1
        return ret+1
    }).next((ret)=>{
        console.log(ret)//2
        return ret+1
    })

    其实早在很久之前就出现了这种理念,并且称之Promise
    所以各语言以及实现的关键字相对一致
    js也出现了许多抽象该逻辑的代码库,如
    bluebird http://bluebirdjs.com/docs/getting-started.html
    promises/A+ https://promisesaplus.com/
    jquery https://api.jquery.com/promise/
    注意,这个时候已经基本出现了流的概念。
    即将代码的执行流程视为一条河流
    A-》B-》C-》D
    后来官方也实现了promise特性,让我们用Promise来实现一下例子!

    function sleep() {
      return new Promise((resolve) => setTimeout(resolve, 3000));
    }
    console.log("我执行了");
    sleep().then(()=>{
      console.log("我等待了3s");
      return sleep()
    }).then(()=>{
      console.log("我等待了6s");
      return sleep()
    }).then(()=>{
      console.log("我等待了9s");
      return sleep()
    })

    到这里相当于将callback进行了一次解耦
    把相同的逻辑给聚合到了一个函数中方便复用以及关注点聚合到了一起
    将厄运金字塔的结构已经理成了一个流型结构,但是依然存在大量的回调,这个我们放到后面来谈。

    Promise的实现相当于一种依赖反转
    即从我们来控制函数的回调变成了我们传入函数和回调
    何时进行回调,什么时候开始回调,由Promise这个函数来进行控制
    记住这个感觉!
    现在我们总结一下
    1.Promise是一种通过对异步函数包裹来解决回调地狱的手段
    2.Promise符合依赖倒置思想,由我们控制回调,变成了我们传入异步函数和回调函数,Promise来控制回调
    3.Promise符合流的概念(即按顺序执行代码块,如A-》B-》C-》D)
    相信到这里你已经理解到1和3
    我们继续基于2进行推导
    既然是由Promise函数来控制回调,那是不是代表我可以添加更多的回调条件,如
    传入一个列表,列表全部的函数完成才回调
    第一个函数完成就回调
    balabalabala
    原生统计了一些常见的需求
    所以出现了
    Promise.all 全部成功回调
    Promise.race 第一个成功就回调等需求
    当然,只要你想,你可以自己实现一个类似Promise的库
    必须N个失败Y个成功才可以开始执行回调~

    为什么引入async

    如果我们函数需要返回一个promise,我们要得到一个结果
    我们就需要不停的创建Promise以及回调,然后返回,再在下方创建一个新的回调
    本质上来说相当于一个流式的回调地狱
    于是为了解决这种繁琐的声明
    出现了async和await
    在async函数内可以直接对promise进行阻塞,这样就可以得到一个干净整洁的代码了~

        (async function() {
            await sleep();
            await sleep();
        })();

    到这里才是真正展平了回调地狱
    更多的资料可以参考2楼cxxjackie大神的代码

    一些小补充

    当声明了一个async表明是异步函数
    在异步函数使用await可以对一个promise在当前函数进行阻塞
    同时返回也变成了一个promise,代表这个异步函数的结果
    因为返回变成了promise
    自然async糖也就具有污染性了

    另外当使用await的时候如果promise出现reject,我们必须使用try-catch进行捕捉
    当然如果大量代码都需要可以使用babel/webpack插件处理

    下一代的异步工具库

    这里我个人比较看好Rxjs
    基本继承了流的概念,同时包含了响应式编程以及函数式编程
    算是很符合当下热潮的一个库
    如我们注册一个事件监听器

    document.addEventListener('click', () => console.log('Clicked!'));

    在rxjs将视为一个流的对象,这个流不断地传出事件触发订阅

    
    import { fromEvent } from 'rxjs';
    
    fromEvent(document, 'click').subscribe(() => console.log('Clicked!'));

    那我们就基本介绍完毕了~

    结语

    撒花~

    引用文档

    https://stackoverflow.com/questions/43712705/why-does-typescript-use-like-types
    http://bluebirdjs.com/docs/why-promises.html

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2023-2-16 22:56:29 | 显示全部楼层
    Promise的.then的写法本身也是一种回调,并不能规避回调地域:
    1. sleep().then(() => {
    2.     sleep().then(() => {
    3.     });
    4. });
    复制代码

    真正将回调展平的是async/await:
    1. (async function() {
    2.     await sleep();
    3.     await sleep();
    4. })();
    复制代码

    我认为Promise更大的意义在于,他可以处理一些“不确定性”问题,比如我们想在页面load以后执行代码,但不确定目前是否已加载完成:
    1. if (document.readyState === 'complete') {
    2.     runScript();
    3. } else {
    4.     window.addEventListener('load', runScript);
    5. }
    复制代码

    这个写法的问题在于,我们被迫将后续代码封装成了runScript函数,如果这样的不确定性持续增加,比如不确定视频是否加载完毕,我们就需要再搞出runScript2、runScript3...这样的代码是很糟糕的,原本完整的逻辑被割裂到多个函数里,不仅别人看不懂,自己维护也费劲。一个自然而然的想法就是,将前置代码封装:
    1. function waitForLoad(next) {
    2.     if (document.readyState === 'complete') {
    3.         next();
    4.     else {
    5.         window.addEventListener('load', next);
    6.     }
    7. }
    复制代码

    然后你的代码看起来就会是这样:
    1. waitForLoad(() => {
    2.     waitForVideo(() => {
    3.     });
    4. });
    复制代码

    用Promise封装后,.then的写法会更加直观:
    1. waitForLoad()
    2.     .then(...)
    3.     .then(...)
    复制代码

    如果加上async/await,代码看起来就更友好了。这就是Promise的意义,也是异步编程的意义,原本割裂的代码得以组合到一起,本质是一种编程思想上的转变。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-2-17 10:10:41 | 显示全部楼层

    cxxjackie 发表于 2023-2-16 22:56

    Promise的.then的写法本身也是一种回调,并不能规避回调地域:

    真正将回调展平的是async/await:

    可以通过then返回一个Promise来拍平的
    比如大佬的写法可以改成

    function sleep() {
      return new Promise((resolve) => setTimeout(resolve, 3000));
    }
    sleep().then(()=>{
      console.log(new Date)
      return sleep()
    }).then(()=>{
      console.log(new Date)
      return sleep()
    }).then(()=>{
      console.log(new Date)
      return sleep()
    })

    将每步封装成promise后是一个线性的
    后边的我没考虑好,晚上有空重新改一下
    还是不太擅长纯理论的😭
    我基础偏差

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2023-2-17 22:25:24 | 显示全部楼层
    李恒道 发表于 2023-2-17 10:10
    [md]可以通过then返回一个Promise来拍平的
    比如大佬的写法可以改成
    ```js

    这个就是我后面waitForLoad的例子嘛,本质上代码还是割裂的,async/await才是正途啊。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-2-18 16:02:01 | 显示全部楼层
    cxxjackie 发表于 2023-2-17 22:25
    这个就是我后面waitForLoad的例子嘛,本质上代码还是割裂的,async/await才是正途啊。 ...

    c大有兴趣基于我的改一下或者重新科普一下吗
    我试着改了一下感觉自己写的完全就是逻辑断裂的...
    只是会用,但是谈到理论完全整个人都是懵的....
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2023-2-19 22:38:50 | 显示全部楼层
    李恒道 发表于 2023-2-18 16:02
    c大有兴趣基于我的改一下或者重新科普一下吗
    我试着改了一下感觉自己写的完全就是逻辑断裂的...
    只是会用 ...

    正常的,Promise就是会有这样的问题,这个只在于写的形式,能否弱化这种割裂感,当然是很主观的东西,我的观点是尽量用async/await。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-2-20 10:53:43 | 显示全部楼层
    cxxjackie 发表于 2023-2-19 22:38
    正常的,Promise就是会有这样的问题,这个只在于写的形式,能否弱化这种割裂感,当然是很主观的东西,我 ...

    okok
    大佬等我再试一下再来点评
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-2-20 21:41:57 | 显示全部楼层

    cxxjackie 发表于 2023-2-19 22:38

    正常的,Promise就是会有这样的问题,这个只在于写的形式,能否弱化这种割裂感,当然是很主观的东西,我 ...

    我又改了一下
    c大
    这次阅读观感会不会好一些

    正文

    后来官方也实现了promise特性,让我们用Promise来实现一下例子!

    function sleep() {
      return new Promise((resolve) => setTimeout(resolve, 3000));
    }
    console.log("我执行了");
    sleep().then(()=>{
      console.log("我等待了3s");
      return sleep()
    }).then(()=>{
      console.log("我等待了6s");
      return sleep()
    }).then(()=>{
      console.log("我等待了9s");
      return sleep()
    })

    到这里相当于将callback进行了一次解耦
    把相同的逻辑给聚合到了一个函数中方便复用以及关注点聚合到了一起
    将厄运金字塔的结构已经理成了一个流型结构,但是依然存在大量的回调,这个我们放到后面来谈。

    为什么引入async

    如果我们函数需要返回一个promise,我们要得到一个结果
    我们就需要不停的创建Promise以及回调,然后返回,再在下方创建一个新的回调
    本质上来说相当于一个流式的回调地狱
    于是为了解决这种繁琐的声明
    出现了async和await
    在async函数内可以直接对promise进行阻塞,这样就可以得到一个干净整洁的代码了~

    (async function() {
        await sleep();
        await sleep();
    })();

    到这里才是真正展平了回调地狱

    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    2022-3-8 11:41
  • 签到天数: 2 天

    [LV.1]初来乍到

    22

    主题

    862

    回帖

    1361

    积分

    荣誉开发者

    积分
    1361

    荣誉开发者卓越贡献油中2周年生态建设者油中3周年挑战者 lv2

    发表于 2023-2-21 20:28:53 | 显示全部楼层
    李恒道 发表于 2023-2-20 21:41
    [md]我又改了一下
    c大
    这次阅读观感会不会好一些

    好,很有精神!另外有个小建议是教程尽量减少专业名词的使用,新手容易摸不着头脑(点名批评正则的前瞻后顾)。
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2023-2-28 23:59
  • 签到天数: 191 天

    [LV.7]常住居民III

    637

    主题

    5196

    回帖

    6078

    积分

    管理员

    非物质文化遗产社会摇传承人

    积分
    6078

    荣誉开发者管理员油中2周年生态建设者喜迎中秋

    发表于 2023-2-21 23:26:05 | 显示全部楼层
    cxxjackie 发表于 2023-2-21 20:28
    好,很有精神!另外有个小建议是教程尽量减少专业名词的使用,新手容易摸不着头脑(点名批评正则的前瞻后 ...

    最近在学设计思想啥的
    有点魔怔了,习惯性就堆出来名词了...
    正则的名词确实太草...
    我一个程序员学正则的时候很多乱七八糟的术语都一脸懵逼
    类似还有柯里化,偏函数,鲁棒性,反射
    不知道哪翻译出来的,感觉好多术语都不讲人话
    混的人。
    ------------------------------------------
    進撃!永遠の帝国の破壊虎---李恒道

    入驻了爱发电https://afdian.net/a/lihengdao666
    个人宣言:この世界で私に胜てる人とコードはまだ生まれていません。死ぬのが怖くなければ来てください。
    回复

    使用道具 举报

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表