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

用 Promise 如何把多个连续的异步过程写得优雅一点?

  •  1
     
  •   SilentDepth · 2016-05-18 17:17:35 +08:00 · 14190 次点击
    这是一个创建于 3115 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如说做一件事分几个动作: step1 、 step2 、 finish ,前两个是异步的, step1 完成后才能执行 step2 (然后 finish )。我想到用 Promise 这么写:

    let prom = new Promise((resolve, reject) => {
      // step1 ,用 setTimeout 模拟一下异步
      setTimeout(() => resolve('1'), 1000);
      // 先忽略 reject 相关动作
    });
    prom.then(data => {
      // step2
      setTimeout(() => {
        prom.then(data => {
          // finish
          console.log(`完成: ${data}`)
        });
      }, 1000);
    });
    

    可这样不就成 callback hell 了吗?而且从 step2 往后传数据也不方便。

    由此引发的还有 step1 出错执行reject()如何中止整个过程的问题。

    还是我对 Promise 的理解有问题?

    36 条回复    2016-05-20 09:35:30 +08:00
    leojoy710
        1
    leojoy710  
       2016-05-18 17:20:10 +08:00   ❤️ 1
    then 可以 return 一个 promise...然后就可以继续 then 了...
    SilentDepth
        2
    SilentDepth  
    OP
       2016-05-18 17:29:24 +08:00
    @leojoy710 是可以链式调用,但不能让 step2 的异步完成后再执行 finish
    spritevan
        3
    spritevan  
       2016-05-18 17:30:25 +08:00
    leojoy710
        4
    leojoy710  
       2016-05-18 17:32:01 +08:00
    @SilentDepth step1.then((d) => step2(d)).then(() => finish())
    napsterwu
        5
    napsterwu  
       2016-05-18 17:37:46 +08:00
    链式调用的时候,你可以用.catch ,任何一步出错都会跳到最后的 reject ,就和 try catch 一样。然后只要你写的每一个 Promise 都有 return Promise ,就可以链式调用。只是我不知道怎么让第一个 Promise 的数据传递到最后,我就用了 block variable 。
    napsterwu
        6
    napsterwu  
       2016-05-18 17:46:34 +08:00
    markmx
        7
    markmx  
       2016-05-18 17:50:28 +08:00
    建议还是使用匿名方法吧..写的太长了...还是不够整齐的.
    Promise 你当做一个流程的管理.不要把逻辑代码过多的放进去
    bdbai
        8
    bdbai  
       2016-05-18 18:22:13 +08:00 via Android
    试试 async/await
    SilentDepth
        9
    SilentDepth  
    OP
       2016-05-18 18:30:40 +08:00
    @leojoy710
    按这个思路试了一下,结果不符预期:
    <script src="https://gist.github.com/SilentDepth/bd558eb503b11f733f97be3322d83b51.js"></script>
    step2 也是一个异步动作, finish 需要 step2 完成后才能执行
    xqin
        10
    xqin  
       2016-05-18 18:50:11 +08:00
    ```

    new Promise((resolve, reject) => { //step 1
    setTimeout(() => resolve('1'), 1000);
    }).then(data => {
    return new Promise((resolve, reject) => { //step 2
    setTimeout(() => resolve([data, '2']), 1000);
    }).then(data => { //finish
    console.log('finish', data);
    });
    });

    ```
    vghdjgh
        12
    vghdjgh  
       2016-05-18 19:20:20 +08:00
    function step1Async() {
    return new Promise((resolve, reject) => {
    step1((error, result) => {
    if (error) {
    reject(error);
    } else {
    resolve(result);
    }
    });
    });
    }
    ...
    try{
    const result = await step1Async();
    await step2Async();
    await finishAsync();
    } catch(error){
    // error
    }
    vghdjgh
        13
    vghdjgh  
       2016-05-18 19:21:17 +08:00
    格式全乱了,看来这里不适合讨论代码。。。
    vghdjgh
        14
    vghdjgh  
       2016-05-18 19:25:09 +08:00
    xjp
        15
    xjp  
       2016-05-18 19:29:18 +08:00
    我一般不喜欢使用 Promise 构造函数 我一般这么用的


    ```
    let prom = new Promise();

    prom.then(data => {
    // step 2
    let prom1 = new Promise();
    setTimeout(() => prom1.resolve('2'), 1000);
    return prom1;
    }).then(data =>{
    // step 3
    return "3";
    }).then(data =>{
    // finish
    console.log('finish', data);
    });

    // step 1
    setTimeout(() => prom.resolve('1'), 1000);


    ```

    如果 then 里 return 的值是 promise 则将 resolve 的结果传入下一个 then

    如果 then 里 return 返回的不是 promise 则将结果直接传入下一个 then
    mcfog
        16
    mcfog  
       2016-05-18 19:43:24 +08:00   ❤️ 1
    这里的陷阱在于你的 setTimeout 是 callback 风格的,不是 promise 风格的

    一定要那 setTimeout 打比方(或者比如外部库是 callback 风格)的话,一定要先转换成 promise 风格

    ```

    function step1() {
    console.log('step1 start');
    return new Promise(resolve => {setTimeout( ()=> {resolve('step1 result');}, 1000)});
    }
    function step2(data) {
    console.log('step2, param is', data);
    return new Promise(resolve => {setTimeout( ()=> {resolve('step2 result');}, 1000)});
    }


    //正常的 promise 串行代码如下
    step1()
    .then(step2)
    .then(result => {
    console.log('finish, param is', result)
    });
    ```

    bluebird 等三方 promise 一般都提供标准的 promisify 方法做这件事
    FlowMEMO
        17
    FlowMEMO  
       2016-05-18 19:57:13 +08:00
    赞同 @mcfog . promise 用的爽前提是你的异步函数已经包成 promise 了,而不是每次都在 then()里生成 promise.
    otakustay
        18
    otakustay  
       2016-05-18 20:47:51 +08:00 via iPad
    核心是你先把 setTimeout 改成返回 Promise 的方法,然后就爽了
    colatin
        19
    colatin  
       2016-05-18 22:03:37 +08:00
    Promise.delay
    jerray
        20
    jerray  
       2016-05-18 23:30:23 +08:00
    支持 generator 吗?如果支持可以参考下这篇 https://www.promisejs.org/generators/
    还有 co https://github.com/tj/co
    leojoy710
        21
    leojoy710  
       2016-05-19 08:16:50 +08:00
    @SilentDepth 原来是我说的太简略...请看 @mcfog 的回复...
    hging
        22
    hging  
       2016-05-19 08:51:12 +08:00 via iPhone
    async 大法好
    SilentDepth
        23
    SilentDepth  
    OP
       2016-05-19 09:41:48 +08:00
    感谢 @napsterwu @xjp @mcfog
    我发现我的问题在于不知道`then()`的参数返回一个 Promise 会并入外部的 Promise composition ( MDN 没有写啊……),看来还是我对 Promise 的理解不到位。

    现在的问题就是中间的`catch()`如何优雅地中止掉整个 composition 。调用`reject()`后会直接跳到后面最近的 rejection handler ,然后就继续走正常路线了。不是应该有个`Promise.prototype.cancel()`之类的方法吗?

    to @xjp : 另外,我说的是 ES2015 里的 Promise ,构造函数的参数必须要有,这个怪我一开始没说清。
    napsterwu
        24
    napsterwu  
       2016-05-19 09:43:19 +08:00
    @SilentDepth Promise.all()
    hantsy
        25
    hantsy  
       2016-05-19 10:13:18 +08:00
    用 RxJS 代替。
    SilentDepth
        26
    SilentDepth  
    OP
       2016-05-19 13:48:45 +08:00
    @napsterwu `Promise.all()`就不是分步了呀
    napsterwu
        27
    napsterwu  
       2016-05-19 13:57:32 +08:00
    @SilentDepth 我上面不是还发了个链接演示怎么分步么
    napsterwu
        28
    napsterwu  
       2016-05-19 13:58:15 +08:00
    napsterwu
        29
    napsterwu  
       2016-05-19 13:58:58 +08:00
    @SilentDepth 抱歉没搞好,左上角要选择 ES6
    SilentDepth
        30
    SilentDepth  
    OP
       2016-05-19 17:39:12 +08:00
    @napsterwu 我是说「 step2 的异步成功后执行 finish 」这个需求用`Promise.all()`不能实现
    SilentDepth
        31
    SilentDepth  
    OP
       2016-05-19 17:55:23 +08:00
    通过空 Promise 实现了一个中止过程功能:
    https://gist.github.com/SilentDepth/67866e565946e84b1409d16d294b6181
    但感觉这样不优雅。

    Any idea?
    yolio2003
        32
    yolio2003  
       2016-05-19 19:47:16 +08:00
    各种大神回复了,楼主还是没明白。。。
    yolio2003
        33
    yolio2003  
       2016-05-19 22:53:59 +08:00
    所以我觉得其实是 promise 本身的问题 (逃
    jimliang
        34
    jimliang  
       2016-05-20 00:08:16 +08:00
    楼主对 Promise 根本不熟练,其次 step1 和 step2 的逻辑就是一样的,可以封装在一起。两行搞定
    ```
    const step = data=>new Promise(resolve=>setTimeout(resolve, 1000, data));
    step('1').then(step).then(data=> console.log(`完成: ${data}`));
    ```
    SilentDepth
        35
    SilentDepth  
    OP
       2016-05-20 09:09:02 +08:00
    @jimliang 刚学。如果是需要各个动作可以独立工作呢?
    jimliang
        36
    jimliang  
       2016-05-20 09:35:30 +08:00
    @SilentDepth 如果是独立的就另外处理。 Promise 的重点是形成 Promise 链, then 的函数要返回具体的对象或者 Promise 作为下一个传递。我看你把一个 Promise then 了两次,也就是说 resolve 后两个 then 都会执行并不会顺着一条链执行了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3543 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 10:54 · PVG 18:54 · LAX 02:54 · JFK 05:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.