31 条回复  ·  3311 次点击
angrylid 小成 前天 15:52
同步代码是逐行往下执行的,碰到网络请求必须等待结果,把后面的计算都阻塞了。 伪代码如下 ``` var image = loadResourceSync('/assets/example.png') canvas.draw(image) // 必须等待上一行得到结果才会往下执行 // 假设后面还有很多代码,都得等着这个网络请求 ``` 这样肯定是不合理的,除依赖这个图像资源的代码之外,其他的代码无须等待这个网络请求。 于是应该是 ``` loadResource('/assets/example.png', (err, data) => { // 这个函数会在网络请求完毕后调用 if(err) { alert(err) return } canvas.draw(data) }) element.innerText = 'Hello World' // 此代码不会等待上面资源加载 ``` 但是这样很容易写出回调地狱,像这样 ``` loadResource('/assets/example.png', (err, data) => { loadResource('/assets/example2.png', (err, data) => { loadResource('/assets/example3.png', (err, data) => { // ... }) }) }) ``` Promise 是一个状态机,帮助你把回调地狱改造成链式调用。 大概像这样 ``` var loadResource = (url) => new Promise((resolve, reject) => { loadResource(url, (err, data) => { if(err) reject(err) else resolve(data) } }) ``` 然后你就可以愉快地用链式地狱来替代回调地狱 ``` loadResource('/assets/example.png') .then((data) => { // ... return loadResource('/assets/example2.png') }) .then((data) => { // ... return loadResource('/assets/example3.png') }) ``` 不严谨地概括一下就是这样,有空你去看一下如何手搓 Promise ,代码其实并不多,就知道怎么回事了。
ychost 小成 前天 15:55
js 是单线程所以 Promise 里面和外面是同一个线程执行的会卡主,他主要是为了解决 ajax 这种回调写起来很麻烦的问题,尤其配合 await 就用同步代码直接去操作 IO 了
kekxv 小成 前天 16:22
如果只是单纯的想不卡住线程的话,这种循环是不行的,它没办法跳出来循环这个逻辑 但是如果在里面加上 await sleep(1),甚至是 0 ,就会发现,主线程似乎不卡死了 但是这种情况下,循环太慢了,那可以考虑每隔 100 次循环、或者 300 次循环进行一次 sleep 操作,也不会明显感知到卡死 如果这时候又觉得加个 sleep 这种操作太过于反人类,那么应该用 worker
buffzty 小成 前天 16:24
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 初学 前天 16:27
感觉你是把异步同步和阻塞非阻塞搞混了,异步≠非阻塞,不要纠结于文字,理解 js 自身依赖于一个单线程的内置事件循环,通过将回调放入事件队列来实现异步操作。 部分 API 间接调用了底层代码,例如网络和 IO ,所以是真·非阻塞调用没问题,而你自己写的代码在同一个线程里运行所以是阻塞的,想要非阻塞代码可以写在 worker 里,这个流程也完全可以放在 promise 里。 promise 自身就是一个威力加强版回调工具,后面还有了 async await 进一步简化,没必要去纠结概念上的问题
musi 初学 前天 16:37
“我理解的异步是 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 小成 前天 16:49
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 小成 前天 16:57
正确用法: // 比如一个 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 小成 前天 17:04
何为“异步编程“?系列三:等待未来 https://zhuanlan.zhihu.com/p/65551936
2218431632 初学 前天 17:21
promise 是解决回调地狱问题啊,promise 你可以塞同步任务,也可以塞异步任务。如果出现 cpu 密集型计算,可以用 worker 解决
返回顶部