แก้ไขสัญญาหนึ่งหลังจากที่อื่น (เช่นในลำดับ)?


269

พิจารณารหัสต่อไปนี้ที่อ่านอาเรย์ของไฟล์ในลักษณะอนุกรม / เรียงลำดับ readFilesส่งคืนสัญญาซึ่งแก้ไขได้เมื่อไฟล์ทั้งหมดถูกอ่านตามลำดับ

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!

  });
};

โค้ดด้านบนใช้งานได้ แต่ฉันไม่ต้องการเรียกซ้ำเพื่อให้สิ่งต่าง ๆ เกิดขึ้นตามลำดับ มีวิธีที่ง่ายกว่าที่จะเขียนโค้ดนี้ใหม่เพื่อที่ฉันไม่ต้องใช้readSequentialฟังก์ชั่นแปลก ๆ ของฉันหรือไม่?

เดิมฉันพยายามใช้Promise.allแต่นั่นทำให้การreadFileโทรทั้งหมดเกิดขึ้นพร้อมกันซึ่งไม่ใช่สิ่งที่ฉันต้องการ:

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

2
สิ่งที่ต้องรอก่อนที่การดำเนินการแบบอะซิงโครนัสจะเสร็จสิ้นจะต้องทำในการเรียกกลับ การใช้สัญญาจะไม่เปลี่ยนแปลงสิ่งนั้น ดังนั้นคุณต้องการการสอบถามซ้ำ
Barmar

1
FYI นี่ไม่ใช่การเรียกซ้ำทางเทคนิคเนื่องจากไม่มีการสร้างเฟรมสแต็ก ก่อนหน้านี้readFileSequential()ได้ถูกส่งคืนแล้วก่อนที่จะเรียกอันถัดไป (เนื่องจากมันเป็นแบบอะซิงโครนัสจึงเสร็จสมบูรณ์นานหลังจากที่การเรียกฟังก์ชั่นดั้งเดิมกลับมาแล้ว)
jfriend00

1
@ jfriend00 ไม่ต้องการการสะสมเฟรมสแต็กสำหรับการเรียกซ้ำ - เป็นการอ้างอิงตนเองเท่านั้น นี่เป็นเพียงเทคนิคเท่านั้น
Benjamin Gruenbaum

3
@BenjaminGruenbaum - ประเด็นของฉันคือว่าไม่มีอะไรผิดปกติกับการมีฟังก์ชั่นเรียกตัวเองที่จะเริ่มต้นซ้ำต่อไป มีข้อเสียเป็นศูนย์และในความเป็นจริงมันเป็นวิธีที่มีประสิทธิภาพในการจัดลำดับการดำเนินการ async ดังนั้นจึงไม่มีเหตุผลที่จะหลีกเลี่ยงสิ่งที่ดูเหมือนการเรียกซ้ำ มีวิธีแก้ปัญหาแบบเรียกซ้ำสำหรับปัญหาบางอย่างที่ไม่มีประสิทธิภาพ - นี่ไม่ใช่หนึ่งในนั้น
jfriend00

1
สวัสดีต่อการสนทนาและการร้องขอในห้อง JavaScript ฉันได้แก้ไขคำตอบนี้เพื่อให้เราสามารถชี้ให้คนอื่นเห็นว่าเป็นเรื่องที่ยอมรับ หากคุณไม่เห็นด้วยโปรดแจ้งให้เราทราบแล้วฉันจะคืนค่าและเปิดอีกหนึ่งตัว
Benjamin Gruenbaum

คำตอบ:


337

อัปเดต 2017 : ฉันจะใช้ฟังก์ชั่น async หากสภาพแวดล้อมรองรับ:

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

หากคุณต้องการคุณสามารถเลื่อนการอ่านไฟล์จนกว่าคุณต้องการใช้ตัวสร้าง async (หากสภาพแวดล้อมของคุณรองรับ)

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

ปรับปรุง: ในความคิดที่สอง - ฉันอาจใช้สำหรับวงแทน:

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

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

หรือขนาดกะทัดรัดกว่าด้วยการลด:

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

ในห้องสมุดสัญญาอื่น ๆ (เช่นเมื่อใดและ Bluebird) คุณมีวิธีการใช้งานสำหรับสิ่งนี้

ตัวอย่างเช่น 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 รอวันนี้


2
@ EmreTapcıไม่ ฟังก์ชันลูกศร "=>" แสดงถึงการส่งคืนแล้ว
สูงสุด

ถ้าคุณใช้ TypeScript ฉันคิดว่า "for in" loop solution ดีที่สุด ลดผลตอบแทนสัญญาซ้ำเช่น ประเภทการโทรกลับแรกคือ Promise <void> จากนั้นที่สองคือ Promise <Promise <void>> เป็นต้น - มันเป็นไปไม่ได้ที่จะพิมพ์โดยไม่ใช้อะไรที่ฉันคิดว่า
Artur Tagisow

@ArturTagisow TypeScript (อย่างน้อยเวอร์ชันใหม่) มีประเภทซ้ำและควรแก้ไขประเภทอย่างถูกต้องที่นี่ ไม่มีสิ่งดังกล่าวในฐานะที่เป็นสัญญา <Promise <T>> เนื่องจากสัญญาว่า "ดูดซึมซ้ำ ๆ " เป็นเหมือนPromise.resolve(Promise.resolve(15)) Promise.resolve(15)
Benjamin Gruenbaum


72

นี่คือวิธีที่ฉันต้องการทำงานในซีรีส์

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;
}

8
และในกรณีที่คุณไม่ทราบจำนวนงานที่แน่นอนล่ะ
damd

1
และเมื่อคุณรู้จำนวนงาน แต่เฉพาะที่รันไทม์เท่านั้น
joeytwiddle

10
"คุณไม่ต้องการที่จะดำเนินการกับสัญญามากมายเลยตามข้อกำหนดของสัญญาทันทีที่มีการสร้างสัญญามันจะเริ่มดำเนินการดังนั้นสิ่งที่คุณต้องการจริงๆก็คือโรงงานแห่งสัญญา" ดูข้อผิดพลาดขั้นสูง # 3 ที่นี่: pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
edelans

5
หากคุณต้องการลดเสียงรบกวนของสายคุณสามารถเขียนได้result = result.then(task);
Daniel Buckmaster

1
@DanielBuckmaster ใช่ แต่ระวังเพราะถ้า task () ส่งคืนค่ามันจะถูกส่งไปยังการเรียกใช้ครั้งถัดไป หากงานของคุณมีข้อโต้แย้งเพิ่มเติมอาจทำให้เกิดผลข้างเคียง รหัสปัจจุบันกลืนกินผลลัพธ์และเรียกใช้งานถัดไปอย่างชัดเจนโดยไม่มีอาร์กิวเมนต์
JHH

63

คำถามนี้เก่า แต่เราอาศัยอยู่ในโลกของ ES6 และจาวาสคริปต์ที่ใช้งานได้ดังนั้นเรามาดูกันว่าเราสามารถปรับปรุงได้อย่างไร

เพราะสัญญาดำเนินการทันทีเราไม่สามารถสร้างอาร์เรย์ของสัญญาได้พวกเขาทั้งหมดจะดับไปในแบบคู่ขนาน

เราต้องสร้างฟังก์ชันที่ส่งคืนสัญญาแทน แต่ละฟังก์ชั่นจะถูกดำเนินการตามลำดับซึ่งจะเริ่มสัญญาภายใน

เราสามารถแก้ปัญหานี้ไม่กี่วิธี reduceแต่วิธีที่ชื่นชอบของฉันคือการใช้งาน

มันเป็นการใช้เล่ห์เหลี่ยมนิดหน่อยreduceเมื่อรวมกับคำสัญญาดังนั้นฉันจึงได้ซับซับลงไปในกัดย่อยเล็ก ๆ ด้านล่าง

สาระสำคัญของฟังก์ชั่นนี้คือการใช้reduceเริ่มต้นด้วยค่าเริ่มต้นPromise.resolve([])หรือสัญญาที่มีอาร์เรย์ที่ว่างเปล่า

สัญญานี้แล้วจะถูกส่งผ่านเข้าสู่วิธีการเป็นreduce promiseนี่คือกุญแจสำคัญในการผูกมัดแต่ละสัญญาเข้าด้วยกันตามลำดับ คำสัญญาต่อไปที่จะดำเนินการคือfuncและเมื่อthenไฟไหม้ผลลัพธ์จะถูกต่อกันและสัญญานั้นจะถูกส่งคืนโดยดำเนินการตามreduceวัฏจักรด้วยฟังก์ชันสัญญาถัดไป

เมื่อสัญญาทั้งหมดได้ดำเนินการแล้วสัญญาที่ส่งคืนจะมีอาร์เรย์ของผลลัพธ์ทั้งหมดของแต่ละสัญญา

ตัวอย่าง ES6 (หนึ่งซับ)

/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

ตัวอย่าง ES6 (แยกย่อย)

// broken down to for easier understanding

const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))

การใช้งาน:

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))

// execute them serially
serial(funcs)
    .then(console.log.bind(console))

1
ดีมากขอบคุณArray.prototype.concat.bind(result)เป็นส่วนที่ฉันหายไปต้องผลักดันผลลัพธ์ด้วยตนเองซึ่งทำงานได้ แต่ก็เท่กว่านี้
zavr

เนื่องจากเราทุกคนเกี่ยวกับ JS ที่ทันสมัยฉันเชื่อว่าconsole.log.bind(console)คำสั่งในตัวอย่างสุดท้ายของคุณตอนนี้มักจะไม่จำเป็น console.logวันนี้คุณก็สามารถส่งผ่าน เช่น. serial(funcs).then(console.log). ทดสอบกับ nodejs และ Chrome ปัจจุบัน
Molomby

นี่เป็นเรื่องยากเล็กน้อยที่จะคลุมหัวฉัน แต่การลดกำลังทำสิ่งนี้ถูกต้อง? Promise.resolve([]).then((x) => { const data = mockApi('/data/1'); return Promise.resolve(x.concat(data)) }).then((x) => { const data = mockApi('/data/2'); return Promise.resolve(x.concat(data)); });
danecando

@Danecando ใช่มันดูถูกต้อง นอกจากนี้คุณยังสามารถวาง Promise.resolve ในการส่งคืนค่าใด ๆ ที่ส่งคืนจะได้รับการแก้ไขโดยอัตโนมัติเว้นแต่คุณจะเรียก Promise.reject
joelnet

@joelnet ในการตอบสนองต่อความคิดเห็นของ Danecando ฉันคิดว่าการลดความน่าจะเป็นสิ่งที่ถูกต้องมากขึ้นในการแสดงออกต่อไปนี้คุณเห็นด้วยไหม? Promise.resolve([]).then(x => someApiCall('url1').then(r => x.concat(r))).then(x => someApiCall('url2').then(r => x.concat(r)))และอื่น ๆ
bufferoverflow76

37

เมื่อต้องการทำสิ่งนี้ใน 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;
}

1
ดูเหมือนว่าใช้ขีดเส้นใต้ คุณสามารถลดความซับซ้อนได้files.forEachถ้าไฟล์เป็นอาร์เรย์
Gustavo Rodrigues

2
อืม ...มันเป็น ES5 for (file of files) {...}วิธีที่จะ ES6
Gustavo Rodrigues

1
คุณบอกว่าคุณไม่ควรใช้Promise.resolve()เพื่อสร้างสัญญาที่ได้รับการแก้ไขแล้วในชีวิตจริง ทำไมจะไม่ล่ะ? ดูเหมือนว่าสะอาดกว่าPromise.resolve() new Promise(success => success())
canac

8
@canac ขออภัยเป็นเพียงเรื่องตลกที่มีการเล่นคำ ("สัญญาที่ว่างเปล่า .. ") ใช้Promise.resolve();รหัสของคุณอย่างแน่นอน
Shridhar Gupta

1
ทางออกที่ดีและง่ายต่อการติดตาม ฉันไม่ได้ใส่ของฉันไว้ในฟังก์ชั่นดังนั้นการแก้ไขในตอนท้ายแทนที่จะวางreturn sequence;ฉันวางไว้sequence.then(() => { do stuff });
Joe Coyle

25

ใช้ง่ายสำหรับสัญญา Node.js มาตรฐาน:

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

UPDATE

items- contract เป็นแพ็กเกจ NPM ที่พร้อมใช้งานที่ทำเช่นเดียวกัน


6
ฉันชอบที่จะเห็นสิ่งนี้อธิบายในรายละเอียดมากขึ้น
Tyguy7

ฉันได้ให้คำตอบนี้พร้อมคำอธิบายด้านล่าง ขอบคุณ
Sarsaparilla

นี่คือสิ่งที่ฉันทำในสภาพแวดล้อม pre-Node 7 ที่ไม่สามารถเข้าถึง async / await สวยและสะอาด
JHH

11

ฉันต้องทำงานตามลำดับจำนวนมากและใช้คำตอบเหล่านี้เพื่อสร้างฟังก์ชันที่จะจัดการงานตามลำดับใด ๆ ...

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 ตัวเลือก อาร์กิวเมนต์แรกคืออาร์เรย์ที่เราจะทำงาน อาร์กิวเมนต์ที่สองคือภารกิจเองฟังก์ชันที่ส่งคืนสัญญางานถัดไปจะเริ่มขึ้นเมื่อสัญญานี้แก้ไขได้ อาร์กิวเมนต์ที่สามคือการเรียกกลับเพื่อให้ทำงานเมื่องานทั้งหมดเสร็จสิ้น หากไม่มีการโทรกลับผ่านฟังก์ชันจะส่งคืนสัญญาที่สร้างขึ้นเพื่อให้เราสามารถจัดการกับจุดสิ้นสุดได้

นี่คือตัวอย่างการใช้งาน:

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 );

หวังว่ามันจะช่วยประหยัดเวลาบางคน ...


วิธีแก้ปัญหาที่เหลือเชื่อมันเป็นสิ่งที่ดีที่สุดที่ฉันพบในเกือบหนึ่งสัปดาห์ของการลักลอบ ... มันอธิบายได้ดีมากมีชื่อในตรรกะตัวอย่างที่ดี (น่าจะดีกว่า) ฉันสามารถโทรหามันได้อย่างปลอดภัย เท่าที่จำเป็นและมีตัวเลือกในการตั้งค่าการโทรกลับ เพียงแค่นีซ! (เพิ่งเปลี่ยนชื่อเป็นสิ่งที่ทำให้ฉันมีความหมายมากขึ้น) .... คำแนะนำสำหรับผู้อื่น ... คุณสามารถวนวัตถุโดยใช้ 'Object.keys ( myObject )' เป็น 'objects_array' ของคุณ
DavidTaubmann

ขอบคุณสำหรับความคิดเห็นของคุณ! ฉันไม่ได้ใช้ชื่อนั้น แต่ฉันต้องการทำให้ชัดเจนมากขึ้น / ง่ายขึ้นที่นี่
Salketer

5

ทางออกที่ดีที่สุดที่ฉันสามารถหาได้ก็คือbluebirdสัญญา คุณสามารถทำสิ่งPromise.resolve(files).each(fs.readFileAsync);ที่รับประกันได้ว่าสัญญาจะได้รับการแก้ไขตามลำดับ


1
ดียิ่งขึ้น: Promise.each(filtes, fs.readFileAsync). แต่คุณไม่ต้องทำ.bind(fs)เหรอ?
Bergi

ดูเหมือนว่าไม่มีใครที่นี่จะเข้าใจความแตกต่างระหว่างอาเรย์และลำดับว่ามันหมายถึงขนาดไม่ จำกัด
vitaly-t

โปรดทราบว่าอาร์เรย์ใน Javascript ไม่มีส่วนเกี่ยวข้องกับอาร์เรย์ขนาดคงที่ในภาษาสไตล์ C พวกเขาเป็นเพียงวัตถุที่มีการจัดการคีย์ตัวเลขปิดและไม่มีขนาดหรือขีด จำกัด ที่กำหนด ( โดยเฉพาะอย่างยิ่งเมื่อไม่ใช้งานnew Array(int)ทั้งหมดที่ทำไว้ล่วงหน้าlengthคู่ของคีย์ - ค่ามีผลต่อจำนวนดัชนีที่ใช้ในระหว่างการทำซ้ำตามความยาว ผลต่อการจัดทำดัชนีจริงหรือขอบเขตดัชนี)
Mike 'Pomax' Kamermans

4

นี่เป็นรูปแบบอื่นของคำตอบข้างต้นเล็กน้อย ใช้สัญญาพื้นเมือง:

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

คำอธิบาย

ถ้าคุณมีงานเหล่านี้แล้วข้างต้นจะเทียบเท่ากับ[t1, t2, t3] Promise.resolve().then(t1).then(t2).then(t3)มันเป็นพฤติกรรมของการลด

วิธีใช้

ก่อนอื่นคุณต้องสร้างรายการงาน! งานคือฟังก์ชั่นที่ไม่รับอาร์กิวเมนต์ หากคุณต้องการส่งผ่านข้อโต้แย้งไปยังฟังก์ชันของคุณให้ใช้bindวิธีการอื่นเพื่อสร้างงาน ตัวอย่างเช่น:

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)

4

โซลูชันที่ฉันต้องการ:

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)


3

ใช้Array.prototype.reduceและจำไว้ว่าให้ห่อสัญญาของคุณในฟังก์ชั่นมิฉะนั้นพวกเขาจะทำงานแล้ว!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

ดีและง่าย ... คุณควรจะสามารถนำเมล็ดพันธุ์เดิมกลับมาใช้ใหม่เพื่อประสิทธิภาพได้อีกครั้ง

สิ่งสำคัญคือต้องป้องกันอาร์เรย์หรืออาร์เรย์ว่างเปล่าที่มีเพียง 1 องค์ประกอบเมื่อใช้การลดดังนั้นเทคนิคนี้จึงเป็นทางออกที่ดีที่สุดของคุณ:

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

แล้วเรียกว่าชอบ:

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});

2

ฉันสร้างวิธีการง่าย ๆ นี้ในวัตถุสัญญา:

สร้างและเพิ่มวิธีการ Promise.sequence ไปยังวัตถุสัญญา

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.all และ Promise.sequence จะถูกเรียกใช้ในลักษณะเดียวกัน แต่มีความหมายต่างกัน

ความระมัดระวัง

การใช้งานสัญญาตามลำดับมักไม่ใช่วิธีที่ดีในการใช้สัญญา โดยปกติแล้วจะดีกว่าการใช้ Promise.all และให้เบราว์เซอร์เรียกใช้รหัสเร็วที่สุด อย่างไรก็ตามมีกรณีการใช้งานจริง - ตัวอย่างเช่นเมื่อเขียนแอปมือถือโดยใช้ javascript


ไม่คุณไม่สามารถเปรียบเทียบPromise.allและของคุณPromise.sequenceได้ หนึ่งจะรับฟังซ้ำได้ของสัญญาอื่น ๆ ที่ใช้ฟังก์ชั่นมากมายที่กลับมาสัญญา
Bergi

Btw ผมอยากแนะนำให้หลีกเลี่ยงการantipattern สัญญาคอนสตรัค
Bergi

ไม่ทราบว่าใช้ตัววนซ้ำ ควรง่ายพอที่จะเขียนใหม่แม้ว่า คุณช่วยอธิบายได้ไหมว่าทำไมนี่คือ Antipattern ตัวสร้างสัญญา ฉันได้อ่านโพสต์ของคุณที่นี่: stackoverflow.com/a/25569299/1667011
frodeborli

@Bergi ฉันได้อัปเดตรหัสเพื่อรองรับตัววนซ้ำ ฉันยังคงไม่เห็นว่านี่เป็นปฏิปักษ์ โดยทั่วไปแล้ว Antipatterns จะต้องได้รับการพิจารณาเป็นแนวทางในการหลีกเลี่ยงความผิดพลาดในการเขียนโค้ดและเป็นฟังก์ชั่นที่สมบูรณ์แบบในการสร้าง (ห้องสมุด) ฟังก์ชั่นที่ผิดหลักเกณฑ์
frodeborli

ใช่ถ้าคุณคิดว่ามันเป็นฟังก์ชั่นห้องสมุดมันก็โอเค แต่ในกรณีนี้reduceคำตอบของเบนจามินก็ง่ายกว่ามาก
Bergi

2

คุณสามารถใช้ฟังก์ชั่นนี้ที่รับรายชื่อ ContractFactories:

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

โรงงานสัญญาเป็นเพียงฟังก์ชั่นที่เรียบง่ายที่ส่งกลับสัญญา:

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

ใช้งานได้เพราะโรงงานสัญญาไม่ได้สร้างสัญญาจนกว่าจะมีการขอให้ มันทำงานในลักษณะเดียวกันกับฟังก์ชั่นนั้น - ในความเป็นจริงมันเหมือนกัน!

คุณไม่ต้องการที่จะดำเนินการมากกว่าสัญญาทั้งหมด ตามข้อกำหนดของสัญญาทันทีที่สร้างสัญญาจะเริ่มดำเนินการ ดังนั้นสิ่งที่คุณต้องการคือโรงงานสัญญามากมาย ...

หากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับสัญญาคุณควรตรวจสอบลิงค์นี้: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html


2

คำตอบของฉันอยู่บนพื้นฐานของhttps://stackoverflow.com/a/31070150/7542429

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

วิธีนี้จะส่งคืนผลลัพธ์เป็นอาร์เรย์เช่น Promise.all ()

การใช้งาน:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});

2

ฉันชอบคำตอบของ @elelnet จริง ๆ แต่สำหรับฉันแล้วรูปแบบการเข้ารหัสนั้นค่อนข้างยากที่จะแยกแยะดังนั้นฉันใช้เวลาสองสามวันในการพยายามหาวิธีที่ฉันจะแสดงโซลูชันเดียวกันในลักษณะที่อ่านได้มากขึ้นและนี่คือของฉัน ใช้เพียงแค่มีไวยากรณ์ที่แตกต่างและความคิดเห็น

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
  // For every url we return a new function
  return () => {
    return new Promise((resolve) => {
      // random wait in milliseconds
      const randomWait = parseInt((Math.random() * 1000),10)
      console.log('waiting to resolve in ms', randomWait)
      setTimeout(()=>resolve({randomWait, url}),randomWait)
    })
  }
})


const promiseReduce = (acc, next) => {
  // we wait for the accumulator to resolve it's promise
  return acc.then((accResult) => {
    // and then we return a new promise that will become
    // the new value for the accumulator
    return next().then((nextResult) => {
      // that eventually will resolve to a new array containing
      // the value of the two promises
      return accResult.concat(nextResult)
    })
  })
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])

// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
  .then((result) => {
    // let's display the final value here
    console.log('=== The final result ===')
    console.log(result)
  })

2

ตามที่ Bergi สังเกตเห็นฉันคิดว่าทางออกที่ดีที่สุดและชัดเจนคือใช้ BlueBird.each โค้ดด้านล่าง:

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);

2

ก่อนอื่นคุณต้องเข้าใจว่ามีการทำสัญญาในเวลาที่สร้าง
ตัวอย่างเช่นถ้าคุณมีรหัส:

["a","b","c"].map(x => returnsPromise(x))

คุณต้องเปลี่ยนเป็น:

["a","b","c"].map(x => () => returnsPromise(x))

จากนั้นเราจำเป็นต้องสัญญาตามลำดับโซ่:

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

การดำเนินการafter()จะทำให้แน่ใจว่าสัญญาถูกสร้างขึ้น (และดำเนินการ) เฉพาะเมื่อถึงเวลา


1

ฉันใช้รหัสต่อไปนี้เพื่อขยายวัตถุสัญญา มันจัดการการปฏิเสธของสัญญาและส่งกลับอาร์เรย์ของผลลัพธ์

รหัส

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

ตัวอย่าง

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);

1

หากคุณต้องการคุณสามารถใช้การลดเพื่อทำสัญญาต่อเนื่องตัวอย่างเช่น

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

มันจะทำงานเป็นลำดับ


1

ใช้ ES ที่ทันสมัย:

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task;

    results.push(result);
  }

  return results;
};

//...

const readFiles = await series(files.map(readFile));

1

ด้วย Async / Await (หากคุณได้รับการสนับสนุน ES7)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

(คุณต้องใช้การforวนซ้ำและไม่ใช่forEachเพราะการซิงค์ / รอคอยมีปัญหาในการทำงานในการวนรอบแต่ละครั้ง)

ไม่มี Async / Await (ใช้สัญญา)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}

2
ไม่แนะนำให้รออยู่ข้างในแต่ละข้าง
Marcelo Agimóvel

@ MarceloAgimóvel - ฉันได้อัปเดตเพื่อแก้ปัญหาที่จะไม่ทำงานกับforEach(ตามนี้ )
Gil Epshtain

0

บนพื้นฐานของชื่อคำถาม "แก้ไขสัญญาหลังจากนั้นอีกหนึ่ง (เช่นในลำดับ)?" เราอาจเข้าใจว่า OP มีความสนใจในการจัดการลำดับของสัญญาในการชำระมากกว่าการโทรตามลำดับต่อเนื่องse

คำตอบนี้มีให้:

  • เพื่อแสดงให้เห็นว่าการโทรตามลำดับนั้นไม่จำเป็นสำหรับการจัดการการตอบสนองตามลำดับ
  • เพื่อแสดงรูปแบบทางเลือกที่ทำงานได้กับผู้เข้าชมหน้านี้รวมถึง OP หากเขายังสนใจในอีกหนึ่งปีต่อมา
  • แม้จะมีการยืนยันของ OP ว่าเขาไม่ต้องการโทรออกไปพร้อม ๆ กันซึ่งอาจเป็นกรณีจริง ๆ แต่อย่างเท่าเทียมกันอาจเป็นข้อสมมุติบนพื้นฐานของความปรารถนาที่จะจัดการกับการตอบสนองตามลำดับเมื่อชื่อมีความหมาย

หากไม่ต้องการการโทรพร้อมกันอย่างแท้จริงโปรดดูคำตอบของ Benjamin Gruenbaum ซึ่งครอบคลุมการโทรตามลำดับ (ฯลฯ ) อย่างครอบคลุม

หากคุณสนใจ (เพื่อประสิทธิภาพที่ดีขึ้น) ในรูปแบบที่อนุญาตให้มีการโทรพร้อมกันตามด้วยการจัดการการตอบสนองตามลำดับโปรดอ่านต่อไป

มันน่าดึงดูดที่คิดว่าคุณต้องใช้Promise.all(arr.map(fn)).then(fn)(เท่าที่ฉันทำมาหลายครั้ง) หรือน้ำตาลแฟนซีของ Promise lib (โดยเฉพาะอย่างยิ่ง Bluebird's) อย่างไรก็ตาม (มีเครดิตในบทความนี้ ) arr.map(fn).reduce(fn)รูปแบบจะทำงานได้โดยมีข้อดีที่:

  • ทำงานร่วมกับ lib สัญญาใด ๆ - แม้รุ่น jQuery ที่ตรงตาม - เท่านั้น .then()จะถูกใช้
  • กำบังความยืดหยุ่นในการข้ามข้อผิดพลาดหรือหยุดข้อผิดพลาดแล้วแต่ว่าคุณต้องการด้วยโมเดอเรเตอร์ใดบรรทัดหนึ่ง

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.
    });
};

หมายเหตุ: มีเพียงส่วนเดียวเท่านั้น Q()แฟรกเมนต์เป็นค่าเฉพาะสำหรับ Q สำหรับ jQuery คุณต้องแน่ใจว่า readFile () ส่งคืนสัญญา jQuery ด้วย A + libs สัญญาต่างประเทศจะถูกหลอมรวม

ที่สำคัญนี่คือการลดของsequenceสัญญาซึ่งลำดับการจัดการของreadFileสัญญา แต่ไม่สร้างของพวกเขา

และเมื่อคุณหมกมุ่นกับมันมันอาจจะเหลือเชื่อเมื่อคุณรู้ว่า.map()เวทีนั้นไม่จำเป็นจริงๆ! งานทั้งหมด, การโทรแบบขนานและการจัดการแบบอนุกรมในลำดับที่ถูกต้อง, สามารถทำได้โดยreduce()ลำพัง, รวมถึงข้อดีเพิ่มเติมของความยืดหยุ่นเพิ่มเติมเพื่อ

  • แปลงจากการโทรแบบขนาน async เป็นการโทรแบบอนุกรม async เพียงแค่เคลื่อนย้ายหนึ่งบรรทัด - อาจเป็นประโยชน์ในระหว่างการพัฒนา

ที่นี่มันเป็น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.
    });
};

นั่นเป็นรูปแบบพื้นฐาน หากคุณต้องการส่งข้อมูล (เช่นไฟล์หรือการแปลงบางส่วน) ไปยังผู้โทรคุณจะต้องมีตัวแปรที่ไม่รุนแรง


ฉันไม่คิดว่ามันเป็นความคิดที่ดีที่จะตอบคำถามที่ขัดต่อความตั้งใจของ
OPs

1
นี้sequence.then(() => filePromise)สิ่งที่เป็น antipattern - มันไม่เกิดข้อผิดพลาดได้เผยแพร่โดยเร็วที่สุดเท่าที่จะทำได้ (และสร้างunhandledRejectionใน libs ที่สนับสนุนพวกเขา) คุณค่อนข้างควรใช้หรือQ.all([sequence, filePromise]) $.when(sequence, filePromise)พฤติกรรมนี้อาจเป็นสิ่งที่คุณต้องการเมื่อคุณต้องการละเว้นหรือข้ามข้อผิดพลาด แต่อย่างน้อยคุณควรพูดถึงสิ่งนี้ว่าเป็นข้อเสีย
Bergi

@Bergi ฉันหวังว่า OP จะเข้ามาและตัดสินว่าสิ่งนี้ขัดกับความตั้งใจของเขาหรือไม่ ถ้าไม่ฉันจะลบคำตอบที่ฉันเดาในขณะที่ฉันหวังว่าฉันจะพิสูจน์ตำแหน่งของฉัน ขอขอบคุณที่กรุณาดำเนินการอย่างจริงจังเพื่อให้ข้อเสนอแนะที่เหมาะสม คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับรูปแบบการต่อต้านหรือให้การอ้างอิงได้ไหม? เช่นเดียวกันกับบทความที่ฉันพบรูปแบบพื้นฐานหรือไม่
Roamer-1888

1
ใช่รุ่นที่สามของรหัสของเขา (นั่นคือ "ทั้งขนานและต่อเนื่อง") มีปัญหาเดียวกัน "antipattern" ต้องการการจัดการข้อผิดพลาดที่ซับซ้อนและมีแนวโน้มที่จะแนบตัวจัดการแบบอะซิงโครนัสซึ่งทำให้เกิดunhandledRejectionเหตุการณ์ ใน Bluebird คุณสามารถหลีกเลี่ยงปัญหานี้ได้โดยใช้sequence.return(filePromise)สิ่งที่มีพฤติกรรมเหมือนกัน แต่จัดการกับการปฏิเสธได้ดี ฉันไม่รู้การอ้างอิงใด ๆ ฉันเพิ่งจะมากับมัน - ฉันไม่คิดว่า "รูปแบบ (ต่อต้าน)" มีชื่อยัง
Bergi

1
@Bergi คุณสามารถเห็นได้ชัดเจนสิ่งที่ฉันไม่สามารถ :( ฉันสงสัยว่าใหม่นี้ความต้องการต่อต้านรูปแบบที่จะได้รับการบันทึกบาง?
ผู้เดินเตร่-1888

0

แนวทางของคุณไม่เลว แต่มีสองประเด็น: มันกลืนข้อผิดพลาดและใช้ Antipattern สัญญาการก่อสร้างที่ชัดเจน

คุณสามารถแก้ไขปัญหาทั้งสองนี้และทำให้ตัวล้างโค้ดในขณะที่ยังใช้กลยุทธ์ทั่วไปเหมือนกัน:

var Q = require("q");

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

var readFiles = function(files) {
  var readSequential = function(index) {
    if (index < files.length) {
      return readFile(files[index]).then(function() {
        return readSequential(index + 1);
      });
    }
  };

  // using Promise.resolve() here in case files.length is 0
  return Promise.resolve(readSequential(0)); // Start!
};

0

หากบุคคลอื่นต้องการวิธีการที่รับประกันได้ว่าเป็นวิธีที่ต่อเนื่องกันอย่างเข้มงวดในการแก้ไขสัญญาเมื่อดำเนินการ CRUD คุณยังสามารถใช้รหัสต่อไปนี้เป็นพื้นฐาน

ตราบใดที่คุณเพิ่ม 'return' ก่อนที่จะเรียกแต่ละฟังก์ชั่นอธิบาย Promise และใช้ตัวอย่างนี้เป็นพื้นฐานในการเรียกใช้ฟังก์ชั่น. next () ถัดไปจะเริ่มอย่างต่อเนื่องหลังจากเสร็จสิ้นการก่อนหน้านี้:

getRidOfOlderShoutsPromise = () => {
    return readShoutsPromise('BEFORE')
    .then(() => {
        return deleteOlderShoutsPromise();
    })
    .then(() => {
        return readShoutsPromise('AFTER')
    })
    .catch(err => console.log(err.message));
}

deleteOlderShoutsPromise = () => {
    return new Promise ( (resolve, reject) => {
        console.log("in deleteOlderShouts");
        let d = new Date();
        let TwoMinuteAgo = d - 1000 * 90 ;
        All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
            if (err) reject();
            console.log("DELETED OLDs at "+d);
            resolve();        
        });
    });
}

readShoutsPromise = (tex) => {
    return new Promise( (resolve, reject) => {
        console.log("in readShoutsPromise -"+tex);
        All_Shouts
        .find({})
        .sort([['dateTime', 'ascending']])
        .exec(function (err, data){
            if (err) reject();
            let d = new Date();
            console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
            resolve(data);
        });    
    });
}

0

สามารถใช้วิธีพุชและป๊อปในลำดับของสัญญาได้ คุณสามารถผลักดันสัญญาใหม่เมื่อคุณต้องการข้อมูลเพิ่มเติม นี่คือรหัสที่ฉันจะใช้ในการโหลด React Infinite เพื่อโหลดลำดับของหน้า

var promises = [Promise.resolve()];

function methodThatReturnsAPromise(page) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(`Resolve-${page}! ${new Date()} `);
			resolve();
		}, 1000);
	});
}

function pushPromise(page) {
	promises.push(promises.pop().then(function () {
		return methodThatReturnsAPromise(page)
	}));
}

pushPromise(1);
pushPromise(2);
pushPromise(3);


0

คำตอบส่วนใหญ่ไม่ได้รวมผลลัพธ์ของสัญญาทั้งหมดเป็นรายบุคคลดังนั้นในกรณีที่มีใครบางคนกำลังมองหาพฤติกรรมนี้โดยเฉพาะนี่เป็นทางออกที่เป็นไปได้โดยใช้การสอบถามซ้ำ

มันเป็นไปตามสไตล์ของPromise.all:

  • ส่งคืนอาร์เรย์ของผลลัพธ์ในการ.then()เรียกกลับ

  • หากสัญญาไม่สำเร็จก็จะส่งคืนทันทีในการ.catch()ติดต่อกลับ

const promiseEach = (arrayOfTasks) => {
  let results = []
  return new Promise((resolve, reject) => {
    const resolveNext = (arrayOfTasks) => {
      // If all tasks are already resolved, return the final array of results
      if (arrayOfTasks.length === 0) return resolve(results)

      // Extract first promise and solve it
      const first = arrayOfTasks.shift()

      first().then((res) => {
        results.push(res)
        resolveNext(arrayOfTasks)
      }).catch((err) => {
        reject(err)
      })
    }
    resolveNext(arrayOfTasks)
  })
}

// Lets try it 😎

const promise = (time, shouldThrowError) => new Promise((resolve, reject) => {
  const timeInMs = time * 1000
  setTimeout(()=>{
    console.log(`Waited ${time} secs`)
    if (shouldThrowError) reject(new Error('Promise failed'))
    resolve(time)
  }, timeInMs)
})

const tasks = [() => promise(1), () => promise(2)]

promiseEach(tasks)
  .then((res) => {
    console.log(res) // [1, 2]
  })
  // Oops some promise failed
  .catch((error) => {
    console.log(error)
  })

หมายเหตุเกี่ยวกับการtasksประกาศอาร์เรย์ :

ในกรณีนี้เป็นไปไม่ได้ที่จะใช้สัญกรณ์ต่อไปนี้เหมือนที่Promise.allจะใช้:

const tasks = [promise(1), promise(2)]

และเราต้องใช้:

const tasks = [() => promise(1), () => promise(2)]

เหตุผลก็คือจาวาสคริปต์จะเริ่มดำเนินการตามสัญญาทันทีหลังจากประกาศ หากเราใช้วิธีการต่างPromise.allๆ มันจะตรวจสอบว่าสถานะทั้งหมดเป็นfulfilledหรือrejectedแต่ไม่เริ่มต้นการทดสอบ การใช้() => promise()เราหยุดการประมวลผลจนกว่าจะมีการเรียกใช้


0
(function() {
  function sleep(ms) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        return resolve();
      }, ms);
    });
  }

  function serial(arr, index, results) {
    if (index == arr.length) {
      return Promise.resolve(results);
    }
    return new Promise(function(resolve, reject) {
      if (!index) {
        index = 0;
        results = [];
      }
      return arr[index]()
        .then(function(d) {
          return resolve(d);
        })
        .catch(function(err) {
          return reject(err);
        });
    })
      .then(function(result) {
        console.log("here");
        results.push(result);
        return serial(arr, index + 1, results);
      })
      .catch(function(err) {
        throw err;
      });
  }

  const a = [5000, 5000, 5000];

  serial(a.map(x => () => sleep(x)));
})();

นี่คือกุญแจสำคัญคือวิธีที่คุณเรียกใช้ฟังก์ชั่นการนอนหลับ คุณต้องผ่านอาร์เรย์ของฟังก์ชั่นที่ตัวเองส่งกลับสัญญาแทนอาร์เรย์ของสัญญา


-1

นี่คือการขยายวิธีการประมวลผลลำดับของสัญญาในลักษณะที่ทั่วไปมากขึ้นสนับสนุนลำดับไดนามิก / อนันต์ขึ้นอยู่กับการใช้spex ผลที่ตามมา:

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

ไม่เพียง แต่โซลูชันนี้จะทำงานกับลำดับขนาดใดก็ได้ แต่คุณสามารถเพิ่มการควบคุมปริมาณข้อมูลและปรับสมดุลให้กับมันได้อย่างง่ายดาย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.