369

ファイルの配列をシリアル/シーケンシャルに読み取る次のコードを考えてみましょう。readFilespromise を返します。これは、すべてのファイルが順番に読み取られた後にのみ解決されます。

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => {
    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

    readSequential(0); // Start with the first file!
  });
};

上記のコードは機能しますが、順番に発生するために再帰を行わなければならないのは好きではありません。readSequential奇妙な関数を使用する必要がないように、このコードを書き直す簡単な方法はありますか?

もともと私は を使用しようとしましPromise.allたが、それによりすべてのreadFile呼び出しが同時に発生しました。これは私が望むものではありません:

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};
4

34 に答える 34

450

Update 2017 :環境がサポートしている場合は、非同期関数を使用します。

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

必要に応じて、非同期ジェネレーターを使用して必要になるまでファイルの読み取りを延期できます (環境でサポートされている場合)。

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

更新: 考え直しました - 代わりに for ループを使用するかもしれません:

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

または、よりコンパクトに、reduce を使用します。

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

他の promise ライブラリ (when や Bluebird など) には、このためのユーティリティ メソッドがあります。

たとえば、ブルーバードは次のようになります。

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

ただし、今日 async await を使用しない理由はありません。

于 2014-07-05T11:55:26.607 に答える
84

これが、タスクを連続して実行する方法です。

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

より多くのタスクがある場合はどうなりますか? 10?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}
于 2015-06-26T09:54:04.793 に答える
50

これをES6で簡単に行うには:

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}
于 2016-04-17T02:26:09.130 に答える
12

多くの順次タスクを実行する必要があり、これらの回答を使用して、順次タスクを処理する関数を作成しました...

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

この関数は、2 つの引数 + 1 つのオプションを取ります。最初の引数は、作業対象の配列です。2 番目の引数はタスク自体で、promise を返す関数です。次のタスクは、この promise が解決されたときにのみ開始されます。3 番目の引数は、すべてのタスクが完了したときに実行するコールバックです。コールバックが渡されない場合、関数は作成した promise を返すため、終了を処理できます。

使用例を次に示します。

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

誰かの時間を節約できることを願っています...

于 2016-07-13T19:30:31.523 に答える
6

私が理解できた最も良い解決策は、bluebird約束をしたことでした。Promise.resolve(files).each(fs.readFileAsync);プロミスが順番に解決されることを保証するだけです。

于 2015-05-06T07:32:01.073 に答える
6

私の好みの解決策:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

ここで公開されている他のものと根本的に違いはありませんが、次のとおりです。

  • シリーズのアイテムに関数を適用します
  • 結果の配列に解決します
  • async/await を必要としません (2017 年頃、サポートはまだかなり制限されています)
  • アロー関数を使用します。素敵で簡潔

使用例:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

妥当な現行の Chrome (v59) および NodeJS (v8.1.2) でテスト済み。

于 2017-08-01T04:51:25.210 に答える
3

Promise オブジェクトでこの単純なメソッドを作成しました。

Promise.sequence メソッドを作成して Promise オブジェクトに追加する

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

使用法:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

Promise オブジェクトに対するこの拡張の最も良い点は、Promise のスタイルと一貫性があることです。Promise.all と Promise.sequence は同じ方法で呼び出されますが、セマンティクスが異なります。

注意

promise の順次実行は、通常、promise を使用するためのあまり良い方法ではありません。通常は、Promise.all を使用して、ブラウザでコードをできるだけ高速に実行することをお勧めします。ただし、実際の使用例があります。たとえば、JavaScript を使用してモバイル アプリを作成する場合などです。

于 2015-07-29T12:32:33.230 に答える
2

promiseFactories リストを取得するこの関数を使用できます。

function executeSequentially(promiseFactories) {
    var result = Promise.resolve();
    promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
    });
    return result;
}

Promise Factory は、Promise を返す単純な関数です。

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

プロミス ファクトリは要求されるまでプロミスを作成しないため、これが機能します。これは then 関数と同じように機能します – 実際、同じことです!

promise の配列を操作する必要はまったくありません。Promise 仕様によると、promise が作成されるとすぐに実行が開始されます。だから、あなたが本当に欲しいのは、promise factory の配列です...

Promises について詳しく知りたい場合は、次のリンクを確認してください: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html

于 2016-12-22T16:24:33.623 に答える
0

質問のタイトル「Promise を次々に (つまり、順番に) 解決しますか?」に基づいて、OP は、順次呼び出し自体よりも決済時の Promise の順次処理に関心があることがわかります。

この回答が提供されています:

  • 応答の順次処理に順次呼び出しが必要ないことを示すため。
  • このページの訪問者に実行可能な代替パターンを公開します。1 年以上経ってもまだ興味がある場合は、OP を含めます。
  • 同時に電話をかけたくないというOPの主張にもかかわらず、これは本当にそうかもしれませんが、タイトルが意味するように応答を順次処理したいという欲求に基づく仮定である可能性もあります。

同時呼び出しが本当に必要ない場合は、順次呼び出し (など) を包括的にカバーする Benjamin Gruenbaum の回答を参照してください。

ただし、(パフォーマンスの向上のために) 同時呼び出しとそれに続く応答の順次処理を可能にするパターンに関心がある場合は、読み進めてください。

Promise.all(arr.map(fn)).then(fn)(私が何度も行ったように) または Promise lib のファンシー シュガー (特に Bluebird のもの)を使用する必要があると考えたくなるかもしれませんが、(この記事の功績により)arr.map(fn).reduce(fn)パターンを使用すると、次のような利点があります。

  • 任意の promise ライブラリで動作します - jQuery の準拠前のバージョンであっても - のみ.then()が使用されます。
  • 1 行の mod で、スキップ オーバー エラーまたはストップ オン エラーのいずれかを柔軟に行うことができます。

のために書かれたものですQ

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

注: その 1 つのフラグメント のみがQ()Q に固有です。jQuery の場合、readFile() が jQuery promise を返すことを確認する必要があります。A+ ライブラリでは、外国の約束が同化されます。

ここで重要なのは、reduction のsequencepromise です。これは、promise の処理readFile​​順序付けますが、作成は順序付けません。

.map()そして、一度それを吸収すると、ステージが実際には必要ないことに気付いたときは、少し気が遠くなるかもしれません! ジョブ全体、並列呼び出しと正しい順序でのシリアル処理は、reduce()単独で達成でき、さらに次の柔軟性が追加されるという利点があります。

  • 1 行移動するだけで、並列非同期呼び出しからシリアル非同期呼び出しに変換できます。開発中に役立つ可能性があります。

ここにありQます。

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

それが基本パターンです。データ (ファイルやその変換など) も呼び出し元に配信したい場合は、軽度のバリアントが必要になります。

于 2015-09-09T01:47:21.373 に答える
0

.then(resPrevTask => nextTask()) を使用するだけです

(ちなみに次のコードは4秒かかります。)

function task1() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(console.log("task 1"))
        }, 3000)
    })
}

function task2() {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(console.log("task 2"))
        }, 1000)
    })
}

function seqTasks(){
    task1()
    .then(() => task2())
}

seqTasks();

于 2022-01-22T23:16:32.273 に答える
0

これを非常にうまく行う npm パッケージPromise Serialがあります。

const Promise_serial = require('promise-serial');

 const promises =
    Array(15).fill()
    .map((_, i) =>
        () => new Promise(resolve => {
            console.log('promise '+i+' start');
            setTimeout(
                () => {
                    console.log('promise '+i+' end');
                    resolve('output-'+i);
                },
                500
            );
        })
    );


console.log('### Run promises in sequence')

Promise_serial(promises)

出力:

promise 0 start
promise 0 end
promise 1 start
promise 1 end
promise 2 start
promise 2 end
promise 3 start
promise 3 end
promise 4 start
promise 4 end
promise 5 start
promise 5 end
promise 6 start
promise 6 end
promise 7 start
promise 7 end

... etc

それらをバッチ化または並列化することもできます。

参照: https://www.npmjs.com/package/promise-serial

于 2021-10-22T05:23:09.700 に答える
-1

これは、 spex.sequence実装に基づいて、動的/無限シーケンスをサポートし、より一般的な方法でプロミスのシーケンスを処理する方法を拡張することです。

var $q = require("q");
var spex = require('spex')($q);

var files = []; // any dynamic source of files;

var readFile = function (file) {
    // returns a promise;
};

function source(index) {
    if (index < files.length) {
        return readFile(files[index]);
    }
}

function dest(index, data) {
    // data = resolved data from readFile;
}

spex.sequence(source, dest)
    .then(function (data) {
        // finished the sequence;
    })
    .catch(function (error) {
        // error;
    });

このソリューションは、任意のサイズのシーケンスで機能するだけでなく、データ スロットリングと負荷分散を簡単に追加できます。

于 2015-10-28T18:01:02.923 に答える