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;
}
你的代码看起来接近你可以在这里最好的。承诺可能是一个习惯的奇怪结构,尤其是当写入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));
值得注意createH1
和appendChild
是同步的功能。这种方法有效地允许你将正常功能链接在一起,模糊同步和异步之间的界限。它也执行/行为完全像您最初发布的代码。
所以是的,这最后一个代码示例可能会稍微有趣。
最后,
一个明显的优点协程有过
.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 = ...
行保留在原来的位置。无论您是否选择内部承诺链,这都是可能的。
你第一次调用addElement()需要另一个'()'来调用内部函数(如我的答案所示)。而且,您不需要额外的承诺就可以按要求的顺序插入项目。这已经由其他解决方案完成。内部功能已按要求的顺序调用。 – jfriend00
我不知道为什么别人留下了一个简单的出路,你可以简单地使用数组和reduce
方法
let promise, inputArray = ['first', 'second', 'third', 'fourth'];
promise = inputArray.reduce((p, element) => p.then(() => addElement(element)), Promise.resolve());
你不需要分配'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' ]
});
您的箭头功能可以简化 - '。于是(X =>的addElement(“第二”))' - 同样,你可以使用箭头功能'addElement' - 但我不知道为什么你认为你正在创建“大量新的关闭” –
我也遇到了这个问题,并最终使用'bind'来代替,尽管它感觉就像凌乱(但避免了额外的函数包装):'.then( addElement.bind(null,“second”))'等等。 –
想知道是否有在这里创建的冗余承诺对象。如果你有3个就足够了,就像你创造6个承诺对象一样?那么已经创建了一个你不能重复使用的Promise对象?让我想想我可能是错的。 – Nishant