Javascript承诺序列
我想按顺序处理一些承诺。我有一个下面,但我想知道我是否有过于复杂的承诺链。我似乎正在创建大量新的闭包,我挠头想知道我是否遗漏了什么 是否有更好的方法编写此函数:Javascript承诺序列,javascript,promise,ecmascript-6,Javascript,Promise,Ecmascript 6,我想按顺序处理一些承诺。我有一个下面,但我想知道我是否有过于复杂的承诺链。我似乎正在创建大量新的闭包,我挠头想知道我是否遗漏了什么 是否有更好的方法编写此函数: 'use strict'; addElement("first") .then(x => {return addElement("second")}) .then(x => { return addElement("third")}) .then(x => { return addElement("fourth")})
'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;
}
您的代码看起来接近您在这里所能获得的最佳代码。承诺可能是一种奇怪的结构,尤其是编写简化的代码往往会最终将一个函数嵌入到另一个函数中。正如你所看到的,这是一个非常常用的短语。只有两种风格的变化是可能的。首先,
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);
}
为了清晰起见,这需要交换承诺链的长度,并且嵌套级别稍微少一些。@Toolbox为您提供了一个很好的答案 为了好玩,我将向你们展示另一种技术,它使用发电机,从中获得灵感 使用它,您的代码将如下所示
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');
}());
使用带有承诺的生成器可以做一些非常有趣的事情。它们在这里并不明显,因为您的addElement
承诺没有解析任何实际值
如果您实际解析了一些值,您可以执行如下操作
// 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
链接相比,协同程序有一个明显的优势,就是所有已解析的承诺都可以在同一范围内访问
比较。然后
链
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 !
}
当然,使用承诺是有道理的,但是哦,天哪,它会很快变丑吗
如果您对以这种方式使用生成器感兴趣,我强烈建议您检查该项目 这里有一篇文章,来自co的创建者
无论如何,我希望您在学习一些其他技术时玩得开心。^\uuu^您可以通过使
addElement()
返回一个函数来简化函数的使用,这样它就可以直接插入。然后()
处理程序,而无需创建匿名函数:
'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)代码>
也许只有我一个人,但我觉得这更令人赏心悦目,尽管这是以额外的.then()为代价的,因此根据addElement()
,还有一个额外的承诺
注意:如果您需要用一个值来解析承诺,您仍然有机会通过从chained-then的回调返回一个值来解析承诺
更进一步说,如果您希望插入的元素按要求的顺序出现,而不是按承诺结算确定的顺序出现,那么您可以同步创建/插入元素,并异步填充它们:
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()
中移动两行,以更改插入的时间,同时将元素留在原来的位置。innerText=…
行。无论您是否选择内部承诺链,这都是可能的。我不确定为什么其他人遗漏了一个简单的方法,您可以简单地使用数组和reduce
方法
let promise, inputArray = ['first', 'second', 'third', 'fourth'];
promise = inputArray.reduce((p, element) => p.then(() => addElement(element)), Promise.resolve());
我在这里写了两种方法:
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(“second”)
-类似地,您可以在addElement
中使用箭头函数-但我不确定您为什么认为您正在创建“大量新闭包”,我也遇到了这个问题,最终使用了bind
,虽然感觉也一样凌乱(但避免了额外的函数包装):。然后(addElement.bind(null,“second”)
,等等。只是想知道这里是否创建了冗余的promise对象。比如你创建了6个promise对象,而3个就足够了?然后已经创建了一个不能重复使用的承诺对象?让我想想我可能错了。@jaromanda啊,你是对的。。。实际上,我将它从最初的形式重构为:function(){return addElement(“second”)}似乎=>是function(){return}的缩写,而不是像我所想的那样仅仅是function(){}。@n我希望这正是我脑子里想的。感觉应该有一种更简单的方法
Sequence.all( [ () => {
return Promise.resolve( 'a' );
}, () => {
return Promise.resolve( 'b' );
} ] ).then( values => {
console.log( values ); // output [ 'a', 'b' ]
} );
Sequence.race( [ () => {
return Promise.reject( 'a' );
}, () => {
return Promise.resolve( 'b' );
}, () => {
return Promise.resolve( 'c' );
} ] ).then( values => {
console.log( values ); // output [ 'a' ]
} );