V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
burnbrid
V2EX  ›  JavaScript

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

  •  
  •   burnbrid · 1 天前 · 2384 次点击

    感觉 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 去执行回调函数。

    44 条回复    2025-03-16 13:15:55 +08:00
    cookii
        1
    cookii  
       1 天前 via Android
    异步和并行是两个概念,单线程也可以异步。
    chiaf
        2
    chiaf  
       1 天前
    把异步代码放进去

    Promise 本身不是异步,他只是个对象,一个容器,用于封装和管理异步操作。

    设计 Promise 不是为了解决回调地狱的问题吗?
    iMusic
        3
    iMusic  
       1 天前
    我觉得 Promise 主要解决的是回调变成了链式。

    关于你举例的这个问题,看下 JS 的事件循环机制,Promise 相关哪些是微任务。
    ShadowPower
        4
    ShadowPower  
       1 天前 via Android
    Promise 不提供异步机制,而是管理异步流程
    renmu
        5
    renmu  
       1 天前 via Android
    promise 不是异步,异步的是网络请求,io 操作
    Linho1219
        6
    Linho1219  
       1 天前 via Android   ❤️ 1
    Promise 构造函数传进去的回调当然是直接同步执行的,但这个回调里面可以有网络请求,或者任何其他的“传统”的通过回调实现的异步操作,这些传统异步操作在成功或失败的时候调用 resolve reject ,实现异步代码执行结果的分发。
    注意分发这个词。Promise 解决的是异步结果的分发问题,方便挂载回调而已,它本身只是一个容器。你可以将其理解为“大闸蟹券”,拿着券去领大闸蟹,但不意味着拿券和拿蟹是异步的(有螃蟹的时候也可以发券)。
    因此 Promise 有两种使用方法:一种就是异步操作的 API 本身就是用 Promise 封装的,这样可以很方便地挂载回调;另一种是将传统的回调式 API 封装成 Promise ,这样可以更方便地挂载回调
    yor1g
        7
    yor1g  
       1 天前
    promise 是回调的标准封装而已
    Linho1219
        8
    Linho1219  
       1 天前 via Android   ❤️ 1
    此外你的理解还有一个严重问题,异步回调还是在主线程中执行的,JS 是单线程的( worker 除外)。可以看看我写的这篇文章 https://notes.linho.cc/s?q=c876458647
    ShadowPower
        9
    ShadowPower  
       1 天前 via Android
    另外,在不在主线程里执行并不能用来判断是不是异步。异步一般跟 IO 有关,主要用于解决线程会被 IO 阻塞的问题。
    这里的 IO 可以是文件,可以是网络,也可以是用户的输入。
    当然,也可以用多线程实现异步,但是并发不会很高。
    june4
        10
    june4  
       1 天前
    你不看 resolve,reject 这二个参数吗,这是可以异步调用的,比如在用户事件回调/网络回调中,这就是异步啊。且你在 await 这个 promise 时会等到这二个参数被调用后才会往下执行,这又解决了回调地狱。
    duanxianze
        11
    duanxianze  
       1 天前
    Promise 本质就是语法糖
    bsg1992
        12
    bsg1992  
       1 天前
    异步 和并行不是一回事的
    Promise 解决的函数回调回调地狱的问题
    且 JS 本身单线程 靠的是 event loop 。
    而你说的这个 是多线程, 在目前浏览器中 只有 worker 和网络请求是单独的线程执行的
    panyang
        13
    panyang  
       1 天前
    所实话,你的理解没有什么大问题。new Promise 中的代码确实是同步执行的,但是传入的 resolve 或 reject 函数会异步触发。

    对于你的困惑,应该是你忘了 resolve 或 reject 函数会异步触发。
    Linho1219
        14
    Linho1219  
       23 小时 53 分钟前 via Android
    @duanxianze Promise 哪里是语法糖???
    luohechen
        15
    luohechen  
       23 小时 41 分钟前   ❤️ 1
    @duanxianze async await 才是语法糖
    PTLin
        16
    PTLin  
       23 小时 28 分钟前
    你要明白 js runtime 的单线程 事件循环模型才能真正的了解 promise 和 js 的 async 。
    搜索关键词:javascript 事件循环
    leonshaw
        17
    leonshaw  
       23 小时 26 分钟前 via Android
    你最终要调用原生的异步方法才是异步
    ns09005264
        18
    ns09005264  
       23 小时 9 分钟前
    我想 javascript 的异步大概是这样发展的:
    1. 最开始是 xmlhttprequest ,然后通过回调函数处理异步结果。
    2. 之后添加了 Promise ,通过链式调用来解决回调地狱,本质就是一个“工具类”,用户也可以手写 Promise ,在语法上没什么特别的。
    3. 添加了 await 和 async 关键字,专门在语法层面改善了 Promise 的链式调用地狱。
    现在的情况是,用来解决回调地狱的 Promise ,被 await 和 async 进一步取代了。


    还能用到 Promise 的地方,这里有一个例子,有许多图片 node 共用一张精灵图,而图片 node 是根据用户滚动视图懒加载的,会先于精灵图的加载展示在页面文档里,因此利用了 Promise 给图片 node 一个保证,等你要展示图片的时候,再叫我。

    https://github.com/MapoMagpie/eh-view-enhance/blob/d77e0655cff35818141b5e6e5246221b1fb12d74/src/platform/ehentai.ts#L211
    在 211 行给 node.delaySrc 设置了 Promise 属性,但是在构造的时候将 Promise 里的 resolve 和 reject 拿走了,接着在 215 行的异步操作的回调函数里去执行 resolve 或 reject 。

    最后在某个将来时刻再使用 node.delaySrc
    https://github.com/MapoMagpie/eh-view-enhance/blob/d77e0655cff35818141b5e6e5246221b1fb12d74/src/img-node.ts#L146
    iOCZS
        19
    iOCZS  
       22 小时 44 分钟前
    promise 是微任务队列,async&await 是协程
    angrylid
        20
    angrylid  
       22 小时 6 分钟前
    同步代码是逐行往下执行的,碰到网络请求必须等待结果,把后面的计算都阻塞了。
    伪代码如下
    ```
    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
        21
    ychost  
       22 小时 4 分钟前
    js 是单线程所以 Promise 里面和外面是同一个线程执行的会卡主,他主要是为了解决 ajax 这种回调写起来很麻烦的问题,尤其配合 await 就用同步代码直接去操作 IO 了
    kekxv
        22
    kekxv  
       21 小时 36 分钟前 via iPhone
    如果只是单纯的想不卡住线程的话,这种循环是不行的,它没办法跳出来循环这个逻辑
    但是如果在里面加上 await sleep(1),甚至是 0 ,就会发现,主线程似乎不卡死了
    但是这种情况下,循环太慢了,那可以考虑每隔 100 次循环、或者 300 次循环进行一次 sleep 操作,也不会明显感知到卡死
    如果这时候又觉得加个 sleep 这种操作太过于反人类,那么应该用 worker
    buffzty
        23
    buffzty  
       21 小时 35 分钟前
    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
        24
    UnluckyNinja  
       21 小时 31 分钟前 via Android
    感觉你是把异步同步和阻塞非阻塞搞混了,异步≠非阻塞,不要纠结于文字,理解 js 自身依赖于一个单线程的内置事件循环,通过将回调放入事件队列来实现异步操作。

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

    promise 自身就是一个威力加强版回调工具,后面还有了 async await 进一步简化,没必要去纠结概念上的问题
    musi
        25
    musi  
       21 小时 21 分钟前 via iPhone
    “我理解的异步是 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
        26
    yazinnnn0  
       21 小时 9 分钟前   ❤️ 2
    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
        27
    buffzty  
       21 小时 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
        28
    charlie21  
       20 小时 54 分钟前
    何为“异步编程“?系列三:等待未来
    https://zhuanlan.zhihu.com/p/65551936
    2218431632
        29
    2218431632  
       20 小时 38 分钟前
    promise 是解决回调地狱问题啊,promise 你可以塞同步任务,也可以塞异步任务。如果出现 cpu 密集型计算,可以用 worker 解决
    nsjs
        30
    nsjs  
       20 小时 4 分钟前
    1. 你被语法给迷惑了,then 和 Promise 在功能上基本是等价的,只是写法不一样
    2. 你的示例代码有问题,你用不用 Promise 都会卡死,你需要了解一下 js 的微队列和宏队列
    3. CPU 密集型作业,最佳解决方案应该是用多线程什么的,而不是你的示例代码的方法(虽然也写错了,但我知道你是想把这种任务分担到 CPU 空闲时做,而不卡死页面)
    nsjs
        31
    nsjs  
       19 小时 56 分钟前
    通过你描述问题的过程,可以看出来你犯的错误是复合型的……不是一句话就能给你讲清楚的。
    异步与同步,协程、线程与进程,动态语言的函数对象,js 的微队列和宏队列,这些你都得看一遍。
    zhouyin
        32
    zhouyin  
       19 小时 16 分钟前
    没有搞清楚通常说的是异步是什么 通常是网络 IO 你的代码时 cpu 密集型代码 cpu 不需要等它从某个地方获得东西后再来执行 而是直接执行代码 同时不能再执行他出的代码了
    jspatrick
        33
    jspatrick  
       17 小时 19 分钟前
    js 的异步需要根据不同时期来看,一开始是通过回调来进行异步处理的,也就是 callback 的形式,但是这样导致了回调地狱的产生,所以有了 promise 的诞生,promise 解决了回调地狱的问题,但是其链式调用的方式,很容易写出面条式的代码,仍然不够符合编程看代码时的直觉,最后才有的 async/await ,用同步的方式写异步
    orangie
        34
    orangie  
       17 小时 11 分钟前 via iPhone
    这种问题我发现真的很适合跟 ChatGPT 交流,它可以用比较通俗但是又能够解释的说法解释,我通过这种方式弄明白了币圈的所谓助记词是怎么回事,还有编程语言中的异步用法等等
    daolanfler
        35
    daolanfler  
       16 小时 59 分钟前 via Android
    去实现一个就知道了,promise 异步只在于 then 里面的回调的执行,至于是放在 queueMicrotask 还是其他微任务甚至宏任务都行
    tcper
        36
    tcper  
       16 小时 51 分钟前
    这个 mdn 文档里有,你这个代码不是异步执行的。
    只有涉及 IO 操作、setTimeout 有限的几个操作是放在 macro task 中执行的异步操作
    heroisuseless
        37
    heroisuseless  
       16 小时 33 分钟前
    zangbianxuegu
        38
    zangbianxuegu  
       16 小时 26 分钟前
    Promise 的构造函数是同步执行的,Promise.then() 的回调会加入微任务队列,异步执行。

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

    刚好今天也写了篇浏览器事件循环的文章,可以看看: https://mp.weixin.qq.com/s/MzAAa4ohk-75BHqGRH1XWQ ,主要是 "In The Loop" presented by Jake Archibald at JSConf.Asia 2018 的内容。
    xbigfat
        39
    xbigfat  
       16 小时 9 分钟前 via Android
    其实我有时候会有个问题 需要请求多个接口合并处理数据,或者根据接口 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
        40
    ilylx2008  
       15 小时 17 分钟前
    谁能给讲讲,从 then()的链式调用进化到 async await 模式,为啥又来了个 rxjs Observable 之类的写法感觉又变回链式了
    yunyuyuan
        41
    yunyuyuan  
       14 小时 56 分钟前
    @ilylx2008 rxjs 和 promise 没有关系,可以把它理解为语法糖


    @xbigfat 你这个需求可以使用 rxjs 的 switchMap 实现
    IvanLi127
        42
    IvanLi127  
       13 小时 33 分钟前
    @ilylx2008 为了实现 stream 模式呗,await 只能收一次值,then 也是,但是 rxjs 能收多次。
    yagamil
        43
    yagamil  
       13 小时 1 分钟前
    我觉得这样才是异步吧

    ```const ps = new Promise(function name(resolve, reject) {
    setTimeout(() => {
    let i = 0;
    while (i < 100) {
    i = i + 1;
    console.log("i=", i);
    }
    resolve();
    }, 1000);
    }).then(() => {
    console.log("promise is done");
    });

    console.log("promise 是异步吗?");
    ```
    xavierchow
        44
    xavierchow  
       43 分钟前
    https://www.promisejs.org/implementing/
    虽然是 7 、8 年前的文章了,可以尝试跟着它自己实现一遍 promise ,会比较清楚它的本质。
    关于题主的问题,promise 和回调有关系,和异步不一定有关系,回调本质上是 A 告诉 B 让 B 在一定的条件下来调用 A ,至于 B 处理本身是不是同步的(比如题主的循环 10 万次),这个和回调(或者 promise )没有必然关系。
    总而言之,JS 的 event loop 机制是非阻塞的(异步),它依赖于回调函数,而 promise 主要是针对处理回调地狱问题的一个解决方案。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2624 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 05:59 · PVG 13:59 · LAX 22:59 · JFK 01:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.