Javascript承诺序列

问题描述:

我想处理序列中的许多承诺。我有一个working piece of code下面,但我想知道如果我已经复杂的承诺链接。我似乎正在创造大量新的关闭,我正在摸索着我的脑袋,想知道我是否错过了一些东西。Javascript承诺序列

有没有写这个功能更好的办法:

'use strict'; 
addElement("first") 
.then(x => {return addElement("second")}) 
.then(x => { return addElement("third")}) 
.then(x => { return addElement("fourth")}) 

function addElement(elementText){ 
    var myPromise = new Promise(function(resolve,reject){ 
     setTimeout(function(){ 
      var element=document.createElement('H1'); 
      element.innerText = `${elementText} ${Date.now()}`; 
      document.body.appendChild(element); 
      resolve(); 
     }, Math.random() * 2000); 
    }); 
return myPromise; 
} 
+3

您的箭头功能可以简化 - '。于是(X =>的addElement(“第二”))' - 同样,你可以使用箭头功能'addElement' - 但我不知道为什么你认为你正在创建“大量新的关闭” –

+0

我也遇到了这个问题,并最终使用'bind'来代替,尽管它感觉就像凌乱(但避免了额外的函数包装):'.then( addElement.bind(null,“second”))'等等。 –

+0

想知道是否有在这里创建的冗余承诺对象。如果你有3个就足够了,就像你创造6个承诺对象一样?那么已经创建了一个你不能重复使用的Promise对象?让我想想我可能是错的。 – Nishant

你的代码看起来接近你可以在这里最好的。承诺可能是一个习惯的奇怪结构,尤其是当写入promis-ified代码通常最终会将函数嵌入到另一个函数中时。正如你可以看到here,这是一个非常常用的措辞。只有两种可能的风格变化。首先,myPromise是不必要的,仅用于添加令人困惑的额外的代码行。直接返回承诺更简单。其次,你可以使用函数绑定在开始时简化你的调用。它可能不在函数本身内部,但它确实消除了几个闭包。这两种变化如下图所示:

'use strict'; 
addElement("first") 
.then(addElement.bind(null,"second")) 
.then(addElement.bind(null,"third")) 
.then(addElement.bind(null,"fourth")) 

function addElement(elementText){ 
    return new Promise(function(resolve,reject){ 
     setTimeout(function(){ 
      var element=document.createElement('H1'); 
      element.innerText = `${elementText} ${Date.now()}`; 
      document.body.appendChild(element); 
      resolve(); 
     }, Math.random() * 2000); 
    }); 
} 

值得指出的是,如果你愿意重组了一下,稍微有吸引力的设计将采取的形式:

'use strict'; 
var myWait = waitRand.bind(null,2000); 
myWait 
    .then(addElement.bind(null, "first")) 
    .then(myWait) 
    .then(addElement.bind(null, "second")) 
    .then(myWait) 
    .then(addElement.bind(null, "third")) 

function waitRand(millis) { 
    return new Promise((resolve, reject) => { 
    setTimeout(resolve, Math.random() * millis); 
    } 
} 

function addElement(elementText) { 
    var element = document.createElement('h1'); 
    element.innerText = `${elementText} ${Date.now()}`; 
    document.body.appendChild(element); 
} 

此交易承诺链的长度为了清楚起见,以及嵌套级别略低。

@TheToolBox对你有一个很好的答案。

只是为了好玩,我将向您展示一种替代技术,使用发电机从coroutines获得灵感。

Promise.prototype.bind = Promise.prototype.then; 

const coro = g => { 
    const next = x => { 
    let {done, value} = g.next(x); 
    return done ? value : value.bind(next); 
    } 
    return next(); 
} 

利用这一点,你的代码将看起来像这样

const addElement = elementText => 
    new Promise(resolve => { 
    setTimeout(() => { 
     var element = document.createElement('H1'); 
     element.innerText = `${elementText} ${Date.now()}`; 
     document.body.appendChild(element); 
     resolve(); 
    }, Math.random() * 2000); 
    }); 

coro(function*() { 
    yield addElement('first'); 
    yield addElement('second'); 
    yield addElement('third'); 
    yield addElement('fourth'); 
}()); 

有一些很有趣的东西,你可以使用发电机,承诺做。它们在这里并不是立即可见的,因为您的承诺不能解决任何实际值。


如果你真的resolve一些值,你可以不喜欢

// sync 
const appendChild = (x,y) => x.appendChild(y); 

// sync 
const createH1 = text => { 
    var elem = document.createElement('h1'); 
    elem.innerText = `${text} ${Date.now()}`; 
    return elem; 
}; 

// async 
const delay = f => 
    new Promise(resolve => { 
    setTimeout(() => resolve(f()), Math.random() * 2000); 
    }); 

// create generator; this time it has a name and accepts an argument 
// mix and match sync/async as needed 
function* renderHeadings(target) { 
    appendChild(target, yield delay(() => createH1('first'))); 
    appendChild(target, yield delay(() => createH1('second'))); 
    appendChild(target, yield delay(() => createH1('third'))); 
    appendChild(target, yield delay(() => createH1('fourth'))); 
} 

// run the generator; set target to document.body 
coro(renderHeadings(document.body)); 

值得注意createH1appendChild是同步的功能。这种方法有效地允许你将正常功能链接在一起,模糊同步和异步之间的界限。它也执行/行为完全像您最初发布的代码。

所以是的,这最后一个代码示例可能会稍微有趣。


最后,

一个明显的优点协程有过.then链,是所有解决承诺可以在同一范围内进行访问。

比较.then链...

op1() 
    .then(x => op2(x)) 
    .then(y => op3(y)) // cannot read x here 
    .then(z => lastOp(z)) // cannot read x or y here 

到协程...

function*() { 
    let x = yield op1(); // can read x 
    let y = yield op2(); // can read x and y here 
    let z = yield op3(); // can read x, y, and z here 
    lastOp([x,y,z]);  // use all 3 values ! 
} 

当然也有workarounds此使用的承诺,但是男孩哦它得到丑陋快...


如果您有兴趣以这种方式使用发电机,我强烈建议您结账co项目。

而这里有一篇文章,Callbacks vs Coroutines,来自的创建者co@tj

无论如何,我希望你有乐趣学习一些其他技术^__^

你可以通过使addElement()返回一个函数代替它可以直接插入.then()处理程序不简化使用的功能必须创建匿名功能:

'use strict'; 
addElement("first")() 
    .then(addElement("second")) 
    .then(addElement("third")) 
    .then(addElement("fourth")) 

function addElement(elementText){ 
    return function() { 
     return new Promise(function(resolve){ 
      setTimeout(function(){ 
       var element=document.createElement('H1'); 
       element.innerText = `${elementText} ${Date.now()}`; 
       document.body.appendChild(element); 
       resolve(); 
      }, Math.random() * 2000); 
     }); 
    } 
} 

关于关闭次数方面没有太多的工作要做。函数嵌套只是你习惯于js的东西,而且问题中的代码真的不是那么糟糕。

正如其他人所说,编写addElement()函数返回函数是一个更整洁的主要承诺链。

稍微进一步,您可能会考虑使用内部承诺链编写返回的函数,从而允许从DOM元素插入中轻微地分离承诺解析。这不会造成更多和更少的关闭,但在语法上更加整洁,尤其是允许您编写setTimeout(resolve, Math.random() * 2000);

'use strict'; 
addElement("first") 
.then(addElement("second")) 
.then(addElement("third")) 
.then(addElement("fourth")); 

function addElement(elementText) { 
    return function() { 
     return new Promise(function(resolve, reject) { 
      setTimeout(resolve, Math.random() * 2000); 
     }).then(function() { 
      var element = document.createElement('H1'); 
      document.body.appendChild(element); 
      element.innerText = `${elementText} ${Date.now()}`; 
     }); 
    }; 
} 

也许这只是我,但我觉得这更令人高兴的眼睛,尽管额外的。然后(成本),因此额外的承诺,每addElement()

注意:如果您需要用值来解决承诺,您仍然有机会通过从链接的回调中返回值来实现此目的。

变本加厉,如果你想插入的元素出现在要求秩序,不承诺解​​决确定的顺序,那么你就可以创建/同步插入元素,并异步填充他们:

function addElement(elementText) { 
    var element = document.createElement('H1'); 
    document.body.appendChild(element); 
    return function() { 
     return new Promise(function(resolve, reject) { 
      setTimeout(resolve, Math.random() * 2000); 
     }).then(function() { 
      element.innerText = `${elementText} ${Date.now()}`; 
     }); 
    }; 
} 

所有这一切都需要在addElement()内移动两行,以改变插入的时间,同时将element.innerText = ...行保留在原来的位置。无论您是否选择内部承诺链,这都是可能的。

+0

你第一次调用addElement()需要另一个'()'来调用内部函数(如我的答案所示)。而且,您不需要额外的承诺就可以按要求的顺序插入项目。这已经由其他解决方案完成。内部功能已按要求的顺序调用。 – jfriend00

我不知道为什么别人留下了一个简单的出路,你可以简单地使用数组和reduce方法

let promise, inputArray = ['first', 'second', 'third', 'fourth']; 

promise = inputArray.reduce((p, element) => p.then(() => addElement(element)), Promise.resolve()); 
+1

你不需要分配'p = p.then(...' –

我写了两个方法在这里:

Sequence = { 
    all(steps) { 
     var promise = Promise.resolve(), 
      results = []; 

     const then = i => { 
      promise = promise.then(() => { 
       return steps[ i ]().then(value => { 
        results[ i ] = value; 
       }); 
      }); 
     }; 

     steps.forEach((step, i) => { 
      then(i); 
     }); 

     return promise.then(() => Promise.resolve(results)); 
    }, 
    race(steps) { 
     return new Promise((resolve, reject) => { 
      var promise = Promise.reject(); 

      const c = i => { 
       promise = promise.then(value => { 
        resolve(value); 
       }).catch(() => { 
        return steps[ i ](); 
       }); 
      }; 

      steps.forEach((step, i) => { 
       c(i); 
      }); 

      promise.catch(() => { 
       reject(); 
      }); 
     }); 
    } 
}; 

Sequence.all会按顺序运行函数,直到解析完参数中的所有承诺。然后返回另一个带有参数的Promise对象,作为一个数组,依次填充所有已解析的值。

Sequence.all([() => { 
    return Promise.resolve('a'); 
},() => { 
    return Promise.resolve('b'); 
} ]).then(values => { 
    console.log(values); // output [ 'a', 'b' ] 
}); 

Sequence.race将按顺序运行函数,并在解析一个promise对象时停止运行。

Sequence.race([() => { 
    return Promise.reject('a'); 
},() => { 
    return Promise.resolve('b'); 
},() => { 
    return Promise.resolve('c'); 
} ]).then(values => { 
    console.log(values); // output [ 'a' ] 
});