Promise被我写嵌套了,请问如何优化?
发布于 8 年前 作者 dgnju 9667 次浏览 来自 问答

写了个了 redis 加锁的函数,写完发现 promise 嵌套了,感觉有点奇怪。

getLock: function (lockName, seconds) {
    var self = this;
    lockName = self.REDIS_LOCK_PREFIX + lockName;
    var expireTimeInMs = Date.now() + seconds * 1000 + 1;
    return self.setnx(lockName, expireTimeInMs).then(function (res) {
        if (res === 1) {
            return true;
        } else {
            return self.get(lockName).then(function (previousExpireInMs) {
                // 过期死锁
                if (previousExpireInMs < Date.now()) {
                    return self.getset(lockName,expireTimeInMs).then(function (getSetReturnExpireInMs) {
                        // 重新设置成功
                        if(previousExpireInMs===getSetReturnExpireInMs){
                            return true
                        }else{
                            return false
                        }
                    }).catch(function () {
                        return false;
                    })
                }else{
                    return false
                }
            }).catch(function (err) {
                return false;
            })
        }
    }).catch(function (err) {
        logger.error(``);
    })
},

假设 setnx,get,getset 都是同步函数,那么我想实现的逻辑如下:

function getLock() {
    var res = setnx(lockName, expireTimeInMs);
    if (res === 1) {
        return true;
    } else {
        var previousExpireInMs = get(lockName);
        if (previousExpireInMs < Date.now()) {
            var getSetReturnExpireInMs = getset(lockName, expireTimeInMs);
            if (previousExpireInMs === getSetReturnExpireInMs) {
                return true
            } else {
                return false
            }
        } else {
            return false
        }
    }
}

我 promise 的写法感觉姿势不对啊,请同学帮忙指出,谢谢。

20 回复

首先

var a = Promise.resolve(1)
var b = a.then((a)=>a)

b 并不是1,你 return 有何意图 。

其次

if (res === 1) {
            return true;
 }
 .....

else 可以去掉

你可以通过这段代码理解。

function(err, data){
if(err)console.log(err);
.....
}

其他的没玩过。

function getDataPromise(){
    return Promise.resolve({ data:{name:'yugo',age:'30',gender:'mail'} })
}

function getData(){
    return getDataPromise().then((done) => { 
        return Object.assign({},done.data,{ name:'miyogurt' })
    })
}

getData().then((data)=>{
    console.log(data)
})

getDataPromise().then((data)=>{
    console.log(data.data)
    return data.data.name;
}).then((name) => {
    console.log("name: " + name)
})


你看下,这个是不是你想要的。

「Promise 对象的 then(onfulfilled) 方法」返回「一个以 onfulfilled 函数的返回值进行 resolve 的 Promise 对象」。记住这点,你就可以把嵌套的 Promise 扁平化了。

then…return…then…return

@MiYogurt getLock 要返回 Promise

@m31271n 假如3个嵌套的 Promise 每一个then 的返回都作为下一个的输入,3个都会执行的情况,很好拉平。到我这例子可能执行完某一个后面就不需要执行了,要能跳出来,困难在这

@alsotang 唐少,请问如果逻辑不需要一路then 到底,中途需要跳出来的情况怎么写呢? ( 如果某一环 reject 跳到 catch 里也觉得很怪)

这Promise写得还不如callback来得简单直接。

@leizongmin 主要是这个对象是提供API的,里面十几个函数都是返回 Promise, 想统一啊

getLock: function (lockName, seconds) {
  lockName = self.REDIS_LOCK_PREFIX + lockName;
  var expireTimeInMs = Date.now() + seconds * 1000 + 1;

  return this.setnx(lockName, expireTimeInMs).then(res => {
    if (res === 1) throw true

    return self.get(lockName)
  }).then(previousExpireInMs => {
    // 过期死锁
    if (previousExpireInMs >= Date.now()) throw false

    return this.getset(lockName,expireTimeInMs)
  }).then(getSetReturnExpireInMs => {
    // 重新设置成功
    if (previousExpireInMs===getSetReturnExpireInMs) throw true

    throw false
  }).catch(err => {
    if (err === true || err === false) {
      return err
    } else {
      throw err
    }
  })
}

@dgnju 我觉得可以像上面这样写: ES6 原生的 Promise 不提供 Promise.break 这种方法。但是可以使用 throw 来终止 Promise 链的继续执行。 最后使用 catch 捕获这些 throw 出的 true/false。

@m31271n 谢谢你的回复。throw 其实等于主动抛异常给 catch 块,这和 代码执行过程中真正的异常混到一块了,感觉也不太好。并且这里 Promise 整体上还是嵌套的。

@dgnju 确实异常混到一起了。刚刚,又处理了一下。

另外,这样,为什么是嵌套的呢?

@leizongmin 谢谢建议,只是上 coroutine 就需要上 generator 配合。 跟现有代码变化太大了。 这里确实不适合 Promise 优雅表达吗?我总感觉自己哪里没写好,想学习下如何处理

@m31271n 我是指 Promise 一层层嵌套起来了。 throw 和我直接 return true 和 false 差别不大。 原来的写法,在调用时 getLock(‘key’, 10).then( function (res) { // 这里也可以直接拿到 true 或 false })

@dgnju 目前 Promise 对分支支持不太友好,用 reject 然后 .then(null,=>console.info()) 捕获怎么样? 用cacth没啥不好的啊,迭代器不也是用的异常实现的嘛?

用co改造后是不是更好理解呢

getLock: function (lockName, seconds) {
        var self = this;
        lockName = self.REDIS_LOCK_PREFIX + lockName;
        var expireTimeInMs = Date.now() + seconds * 1000 + 1;
        return co(function* (){
            let res = yield self.setnx(lockName, expireTimeInMs);
            if (res === 1) {
                return true;
            }

            let previousExpireInMs = yield self.get(lockName);
            // 过期死锁
            if (previousExpireInMs < Date.now()) {
                let getSetReturnExpireInMs = yield self.getset(lockName,expireTimeInMs);
                // 重新设置成功
                if(previousExpireInMs===getSetReturnExpireInMs){
                    return true
                }
            }
            return false;
        }).catch(err=>{
            logger.error(err);
            return false;
        });
    }

首先谢谢楼上各位的回复。 总结下: 如果需要提前跳出 promise 链,那么主要有2种方法:

  1. 在分支处 thow new Error(’’), 或者 return Promise.reject({}), 即抛出一个自己标记过的“异常” 最后在 catch 中统一处理 在 catch 中可以判断异常是不是自己特意标记的,从而与真正的异常分开处理; 具体请参见 @m31271n @Shonke 的回复;
  2. 使用第三方库; 例如:bluebird 支持 cancel 方法; 请参考: http://bluebirdjs.com/docs/api/cancellation.html http://stackoverflow.com/questions/28572180/can-i-break-a-chain-early-with-bluebird-promises https://cnodejs.org/topic/58385d4927d001d606ac197d

如果可以引入 generator 的话,那么配合可以使用 co,或者 Promise.coroutine 等改善; 请参见楼上:@leizongmin @alsotang @chrislbb 等童鞋的回复

提供一种promise的写法, new Promise((res,rej)=>{ res(resObj);//or rej(rejObj); }) .then((data)=>{ return promise_task; }) .then(…) .catch(err=>{ }) promise_task写成这样的: fs.readFile(path,(err,doc)=>{ if(err)throw err; return doc; }) promise_task或者封装成promise化的函数 new Promise((res,rej)=>{ fs.readFile(path,(err,doc)=>{ if(err)rej(‘some error occur’); res(doc); }) }) }); 码字不易

回到顶部