李恒道 发表于 2023-2-16 20:38:10

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

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

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

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

我们想要执行sleep休眠到3s后再执行下一个函数
这个时候如果直接
```js
sleep(3000)
next()
```
是行不通的
那怎么办?
因为JS里函数是一等公民(可以作为函数参数,可以作为函数返回值,也可以赋值给变量。)
所以我们可以把函数传递给sleep函数,让他在执行3s后再执行传入的函数

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

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

```js
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)
这种代码也被戏称为厄运金字塔
!(https://bbs.tampermonkey.net.cn/ ... o5596azhh0zz9ac.png)
但是如果我们抽象一下逻辑,发现虽然是回调地狱,实际执行流程是线性的
A-》B-》C-》D
所以我们可以创造一个对象,这个对象来负责管理异步函数,当异步函数执行完毕后执行回调,回调触发这个对象的下一步操作
伪代码大概是这样的

```js
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来实现一下例子!

```js
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进行阻塞,这样就可以得到一个干净整洁的代码了~

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

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

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

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

这里我个人比较看好Rxjs
基本继承了流的概念,同时包含了响应式编程以及函数式编程
算是很符合当下热潮的一个库
如我们注册一个事件监听器
```js
document.addEventListener('click', () => console.log('Clicked!'));
```

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

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

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

Promise的.then的写法本身也是一种回调,并不能规避回调地域:
sleep().then(() => {
    sleep().then(() => {
    });
});
真正将回调展平的是async/await:
(async function() {
    await sleep();
    await sleep();
})();
我认为Promise更大的意义在于,他可以处理一些“不确定性”问题,比如我们想在页面load以后执行代码,但不确定目前是否已加载完成:
if (document.readyState === 'complete') {
    runScript();
} else {
    window.addEventListener('load', runScript);
}
这个写法的问题在于,我们被迫将后续代码封装成了runScript函数,如果这样的不确定性持续增加,比如不确定视频是否加载完毕,我们就需要再搞出runScript2、runScript3...这样的代码是很糟糕的,原本完整的逻辑被割裂到多个函数里,不仅别人看不懂,自己维护也费劲。一个自然而然的想法就是,将前置代码封装:
function waitForLoad(next) {
    if (document.readyState === 'complete') {
      next();
    else {
      window.addEventListener('load', next);
    }
}
然后你的代码看起来就会是这样:
waitForLoad(() => {
    waitForVideo(() => {
    });
});
用Promise封装后,.then的写法会更加直观:
waitForLoad()
    .then(...)
    .then(...)
如果加上async/await,代码看起来就更友好了。这就是Promise的意义,也是异步编程的意义,原本割裂的代码得以组合到一起,本质是一种编程思想上的转变。

李恒道 发表于 2023-2-17 10:10:41

cxxjackie 发表于 2023-2-16 22:56
Promise的.then的写法本身也是一种回调,并不能规避回调地域:

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


可以通过then返回一个Promise来拍平的
比如大佬的写法可以改成
```js
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后是一个线性的
后边的我没考虑好,晚上有空重新改一下
还是不太擅长纯理论的😭
我基础偏差

cxxjackie 发表于 2023-2-17 22:25:24

李恒道 发表于 2023-2-17 10:10
可以通过then返回一个Promise来拍平的
比如大佬的写法可以改成
```js


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

李恒道 发表于 2023-2-18 16:02:01

cxxjackie 发表于 2023-2-17 22:25
这个就是我后面waitForLoad的例子嘛,本质上代码还是割裂的,async/await才是正途啊。 ...

c大有兴趣基于我的改一下或者重新科普一下吗
我试着改了一下感觉自己写的完全就是逻辑断裂的...
只是会用,但是谈到理论完全整个人都是懵的....

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

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

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

李恒道 发表于 2023-2-20 10:53:43

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

okok
大佬等我再试一下再来点评

李恒道 发表于 2023-2-20 21:41:57

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

我又改了一下
c大
这次阅读观感会不会好一些
# 正文
后来官方也实现了promise特性,让我们用Promise来实现一下例子!

```js
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();
    })();

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

cxxjackie 发表于 2023-2-21 20:28:53

李恒道 发表于 2023-2-20 21:41
我又改了一下
c大
这次阅读观感会不会好一些


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

李恒道 发表于 2023-2-21 23:26:05

cxxjackie 发表于 2023-2-21 20:28
好,很有精神!另外有个小建议是教程尽量减少专业名词的使用,新手容易摸不着头脑(点名批评正则的前瞻后 ...
最近在学设计思想啥的
有点魔怔了,习惯性就堆出来名词了...
正则的名词确实太草...
我一个程序员学正则的时候很多乱七八糟的术语都一脸懵逼
类似还有柯里化,偏函数,鲁棒性,反射
{:4_98:}不知道哪翻译出来的,感觉好多术语都不讲人话
页: [1] 2
查看完整版本: [油猴脚本开发指南番外篇]为什么我们需要Promise