JavaScript 中的 Promise 跟异步有关系吗?还是我的理解有问题?谁能把 Promise 解释清楚? Promise 的正确用法应该是什么样的?

1 天前
 burnbrid

感觉 Promise 跟异步没有关系啊!我理解的异步是 ajax 这样的,ajax 将请求发出去之后,代码就继续往下执行了,等到 ajax 收到响应结果了,再回头执行 ajax 的回调函数,这才是异步。不是说你指定了回调函数就是异步。Promise 构造函数里面的代码是同步执行的,假如在 Promise 构造函数里面执行一个 10 万次的循环,主线程会等这 10 万次执行结束之后,才会继续执行下一行代码,这叫异步吗?异步跟回调函数没有一点关系。 异步就是不在主线程里面执行。

const ps = new Promise(function name(resolve, reject) { let i = 0; while(i<1000000){ i = i+1; console.log('i=',i); } }); console.log('promise 是异步吗?');

等上面这 100 万次循(最耗时的操作)环执行完,我还有必要通过 then 去指定回调函数吗?我直接执行回调函数就可以了,根本不需要通过 then 去执行回调函数。

2854 次点击
所在节点    JavaScript
48 条回复
ychost
1 天前
js 是单线程所以 Promise 里面和外面是同一个线程执行的会卡主,他主要是为了解决 ajax 这种回调写起来很麻烦的问题,尤其配合 await 就用同步代码直接去操作 IO 了
kekxv
1 天前
如果只是单纯的想不卡住线程的话,这种循环是不行的,它没办法跳出来循环这个逻辑
但是如果在里面加上 await sleep(1),甚至是 0 ,就会发现,主线程似乎不卡死了
但是这种情况下,循环太慢了,那可以考虑每隔 100 次循环、或者 300 次循环进行一次 sleep 操作,也不会明显感知到卡死
如果这时候又觉得加个 sleep 这种操作太过于反人类,那么应该用 worker
buffzty
1 天前
js 中的异步操作是 io,setTimeout,setInterval,Promise.proto.then ... 这些
js 的执行逻辑是先执行主线程代码,再执行任务队列中的函数,因为主线程代码会有一些异步操作,遇到就将这个异步操作的回调函数加入任务队列中

new Promise()不是异步操作,Promise.proto.then 才是异步
Promise 一般是去包住异步操作,让代码不变成回调地狱的,你 Promise 中根本没有异步操作 所以不必包一层.
如果想实现你想要的效果可以这样:

new Promise(s => s()).then(_ => {
let i = 0; while (i < 10) { i++; console.log('i=', i); }
}
)
console.log('promise 是异步吗?');
UnluckyNinja
1 天前
感觉你是把异步同步和阻塞非阻塞搞混了,异步≠非阻塞,不要纠结于文字,理解 js 自身依赖于一个单线程的内置事件循环,通过将回调放入事件队列来实现异步操作。

部分 API 间接调用了底层代码,例如网络和 IO ,所以是真·非阻塞调用没问题,而你自己写的代码在同一个线程里运行所以是阻塞的,想要非阻塞代码可以写在 worker 里,这个流程也完全可以放在 promise 里。

promise 自身就是一个威力加强版回调工具,后面还有了 async await 进一步简化,没必要去纠结概念上的问题
musi
1 天前
“我理解的异步是 ajax 这样的,ajax 将请求发出去之后,代码就继续往下执行了,等到 ajax 收到响应结果了,再回头执行 ajax 的回调函数,这才是异步。”
“假如在 Promise 构造函数里面执行一个 10 万次的循环,主线程会等这 10 万次执行结束之后,才会继续执行下一行代码”

你不觉得你这两个就不是一个场景吗?
ajax 在请求发出后只需要等待响应就可以了,这时候 CPU 是空闲的,你这 10 万次循环可是会一直占用 CPU 的。
你学 js 的时候不应该首先学 js 是单线程的么,在单线程中循环 10 万次的计算就是会造成卡顿,就算是天王老子来了这也没有任何优化的办法,直到后面出现了 web worker
我觉得你是没搞清楚异步和线程的概念,js 是通过事件循环机制实现的异步,简单的就是上面的 ajax 场景,发出一个请求后 cpu 进入空闲状态,可以做其他任务,当收到响应时通过事件告知 js 这个 ajax 任务完成了,你可以将 cpu 切换到这里继续做 ajax 之后的任务了。而 promise 就是提供了一套规范让开发者更方便使用这套机制而已,楼上说的语法糖在一定程度上也没错,毕竟在 promise 出现之前异步早就在 js 中被深度使用了
yazinnnn0
1 天前
Promise 借鉴了 Monad 的一些设计思想, 用 CPS 变换优化实现链式组合异步操作(解决异步的回调地狱)
async await 是类似于 Monad Comprehensions 的语法糖实现, 不过运行机制不一样, async await 依赖 javascript 运行时模型


原有的异步调用代码
asyncOp1((result1) => {
asyncOp2(result1, (result2) => {
asyncOp3(result2, (result3) => { /* ... */ });
});
});

Promise 优化之后的代码
asyncOp1()
.then((result1) => asyncOp2(result1))
.then((result2) => asyncOp3(result2))
.then((result3) => { /* ... */ });

使用 Monad Comprehensions 语法糖将中间变量从回调中取出来
result1 = await asyncOp1()
result2 = await asyncOp2(result1)
result3 = await asyncOp3(result2)


题外话, promise 不是标准的 monad, 但是有很多 monad 的特性, 比如统一的异常处理 (async await 之后就可以异步 try catch 了), 不然的话你想想每个回调都传一个 reject 参数来处理异常 (人肉 if err != nil 是吧)

所以说那些 go boy 用 if err != nil 来嘲笑 try catch 就挺逆天的, 学术界用几十年的时间来消除 errcode, 然后这玩意儿在 go boy 嘴里成最优雅的语言了😅

题外话 2, 其他语言的 Monad Comprehensions 语法糖举例
haskell
compute :: Maybe Int
compute = do
x <- Just 3
y <- Just 5
return (x + y) -- 结果为 Just 8

csharp
var query = from x in new[] { 1, 2 }
from y in new[] { 3, 4 }
select x + y; // 结果为 { 4, 5, 5, 6 }

fsharp
let asyncExample = async {
let! x = async { return 3 }
let! y = async { return 5 }
return x + y
}

scala
val compute: Option[Int] = for {
x <- Some(3)
y <- Some(5)
} yield x + y

swift
func compute() async -> Int {
let x = await fetchData()
let y = await processData(x)
return y
}

kotlin
suspend fun compute(): Int {
val x = async { fetchData() }
val y = async { processData(x.await()) }
return y.await()
}

java
没有😅

go
没有😅

rust
没有语法糖, 有个?(操作符)可以模拟类似功能(result option)

基本上后来兴起的工程向的语言都给异步相关的 api 实现了 monad comprehension, 一部分语言(fsharp kotlin 啥的)可以自定义语法

题外话 3
貌似一些 lisp people 极度反感 monad (和 go boy 殊途同归了属于是), clojure 社区里贴大字报明确反对标准库里添加 monad 实现, 吵了好几十条😅
buffzty
1 天前
正确用法:
// 比如一个 api 需要查 2 次数据库 再进行某个操作 一般就这样写
const fn1 = async () => { console.log(1); return 1 };// 一个异步操作
const fn2 = async () => { console.log(2); return 2 };// 一个异步操作
const fn3 = async (a, b) => { console.log(3); return a + b };// 一个操作 异不异步都无所谓 反正都是最后一个操作了

console.log("begin")
const task0 = new Promise((s, j) => {
(async () => {
try {
const task1 = fn1()
const task2 = fn2()
const res1 = await task1
const res2 = await task2
s(await fn3(res1, res2))
} catch (e) {
j(e)
}
})();
})
task0.then(res => {
console.log("res:", res)
}).catch(e => {
console.error(e)
})
console.log("end")

// async 是 new Promise()的语法糖
// await 是 task.then 的语法糖
// return 是 resolve()的语法糖
// 现在一般都用 async 代替 Promise 可以少写几个字母
charlie21
1 天前
何为“异步编程“?系列三:等待未来
https://zhuanlan.zhihu.com/p/65551936
2218431632
1 天前
promise 是解决回调地狱问题啊,promise 你可以塞同步任务,也可以塞异步任务。如果出现 cpu 密集型计算,可以用 worker 解决
nsjs
1 天前
1. 你被语法给迷惑了,then 和 Promise 在功能上基本是等价的,只是写法不一样
2. 你的示例代码有问题,你用不用 Promise 都会卡死,你需要了解一下 js 的微队列和宏队列
3. CPU 密集型作业,最佳解决方案应该是用多线程什么的,而不是你的示例代码的方法(虽然也写错了,但我知道你是想把这种任务分担到 CPU 空闲时做,而不卡死页面)
nsjs
1 天前
通过你描述问题的过程,可以看出来你犯的错误是复合型的……不是一句话就能给你讲清楚的。
异步与同步,协程、线程与进程,动态语言的函数对象,js 的微队列和宏队列,这些你都得看一遍。
zhouyin
1 天前
没有搞清楚通常说的是异步是什么 通常是网络 IO 你的代码时 cpu 密集型代码 cpu 不需要等它从某个地方获得东西后再来执行 而是直接执行代码 同时不能再执行他出的代码了
jspatrick
1 天前
js 的异步需要根据不同时期来看,一开始是通过回调来进行异步处理的,也就是 callback 的形式,但是这样导致了回调地狱的产生,所以有了 promise 的诞生,promise 解决了回调地狱的问题,但是其链式调用的方式,很容易写出面条式的代码,仍然不够符合编程看代码时的直觉,最后才有的 async/await ,用同步的方式写异步
orangie
1 天前
这种问题我发现真的很适合跟 ChatGPT 交流,它可以用比较通俗但是又能够解释的说法解释,我通过这种方式弄明白了币圈的所谓助记词是怎么回事,还有编程语言中的异步用法等等
daolanfler
1 天前
去实现一个就知道了,promise 异步只在于 then 里面的回调的执行,至于是放在 queueMicrotask 还是其他微任务甚至宏任务都行
tcper
1 天前
这个 mdn 文档里有,你这个代码不是异步执行的。
只有涉及 IO 操作、setTimeout 有限的几个操作是放在 macro task 中执行的异步操作
heroisuseless
1 天前
zangbianxuegu
1 天前
Promise 的构造函数是同步执行的,Promise.then() 的回调会加入微任务队列,异步执行。

@Linho1219 大佬解释的比较清楚。

刚好今天也写了篇浏览器事件循环的文章,可以看看: https://mp.weixin.qq.com/s/MzAAa4ohk-75BHqGRH1XWQ ,主要是 "In The Loop" presented by Jake Archibald at JSConf.Asia 2018 的内容。
xbigfat
1 天前
其实我有时候会有个问题 需要请求多个接口合并处理数据,或者根据接口 1 的结果查询接口 2 的结果,到底怎么实现才是科学的?

我现在的实现方式
async function query(){

let a = await fetch().then(res=>{res.json()});

let result =await fetch(a).then(res=>{res.json()});

}

这样合理吗?有时候 result 拿不到结果,debug 一看是 a 没请求到,也就是说并没有等待 a 返回已经执行第二个请求了,也不知道到底咋用
ilylx2008
1 天前
谁能给讲讲,从 then()的链式调用进化到 async await 模式,为啥又来了个 rxjs Observable 之类的写法感觉又变回链式了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1118623

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX