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' ]
} );