Làm cách nào tôi có thể thực thi mảng lời hứa theo thứ tự tuần tự?


81

Tôi có một loạt các lời hứa cần chạy theo thứ tự tuần tự.

var promises = [promise1, promise2, ..., promiseN];

Gọi RSVP.all sẽ thực thi chúng song song:

RSVP.all(promises).then(...); 

Nhưng, làm thế nào tôi có thể chạy chúng theo trình tự?

Tôi có thể xếp chúng theo cách thủ công như thế này

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

nhưng vấn đề là số lượng lời hứa thay đổi và mảng lời hứa được xây dựng động.


từ các câu trả lời khác và phiếu phản đối của tôi, có vẻ như nhiều người cần đọc README rsvp nơi nó giải thích "Phần thực sự tuyệt vời đến khi bạn trả lại lời hứa từ người xử lý đầu tiên". Nếu bạn không làm điều này, bạn thực sự đang bỏ lỡ sức mạnh biểu đạt của những lời hứa.
Michael Johnston

Câu hỏi tương tự nhưng không dành riêng cho khung: stackoverflow.com/q/24586110/245966
jakub.

Câu trả lời:


136

Nếu bạn đã có chúng trong một mảng thì chúng đang thực thi. Nếu bạn có một lời hứa thì nó đã được thực hiện. Đây không phải là mối quan tâm của những lời hứa (IE họ không giống như C # Taskvề mặt .Start()phương pháp). .allkhông thực hiện bất cứ điều gì nó chỉ trả về một lời hứa.

Nếu bạn có một mảng các hàm trả về lời hứa:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

Hoặc các giá trị:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});

3
đây là một cách tuyệt vời để xây dựng một cây các lời hứa thuần nhất mà không yêu cầu đối số. Nó hoàn toàn tương đương với việc sử dụng con trỏ next_promise để tự xây dựng cây, điều bạn cần làm nếu tập hợp các hứa hẹn không đồng nhất về đối số, v.v. Chỉ là hàm giảm đang thực hiện con trỏ đến-hiện -bít trang cho bạn. Bạn cũng sẽ muốn xây dựng cái cây của chính mình nếu một số việc của bạn có thể xảy ra đồng thời. Trong cây lời hứa, cành là chuỗi và lá là đồng thời.
Michael Johnston

Cảm ơn về câu trả lời của bạn. Bạn nói đúng rằng việc tạo một lời hứa đã có nghĩa là nó đang thực thi vì vậy câu hỏi của tôi không được hình thành chính xác. Cuối cùng tôi đã giải quyết vấn đề của mình theo cách khác mà không cần hứa hẹn.
jaaksarv

1
@SSHThis Vâng, trước hết, w. Thứ hai, phản hồi trước đó được chuyển đến .then, trong ví dụ này, nó chỉ bị bỏ qua ...
Esailija

3
Nếu một trong những lời hứa không lỗi sẽ không bao giờ bị từ chối và hứa sẽ không bao giờ giải quyết ...
Maxwelll

5
Nếu bạn đã có chúng trong một mảng thì chúng đang thực thi. - cụm từ này nên được in đậm + phông chữ lớn hơn. Điều quan trọng là phải hiểu.
ducin

22

Với các hàm không đồng bộ của ECMAScript 2017, nó sẽ được thực hiện như sau:

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn()
    }
}

Bạn có thể sử dụng BabelJS để sử dụng các chức năng không đồng bộ ngay bây giờ


Đây sẽ là cách tiếp cận mặc định cho đến nay (2020). Đối với những người dùng lần đầu tiên, có thể cần lưu ý hai điều ở đây: 1. Một khi một lời hứa tồn tại, nó sẽ bắt đầu. Vì vậy, điều thực sự quan trọng là 2. fn1, fn2, fn3đây là các chức năng, ví dụ () => yourFunctionReturningAPromise()như trái ngược với chỉ yourFunctionReturningAPromise(). Đây cũng là lý do tại sao await fn()cần thiết thay vì chỉ await fn. Xem thêm trong tài liệu chính thức . Xin lỗi vì đã đăng dưới dạng nhận xét nhưng hàng đợi chỉnh sửa đã đầy :)
ezmegy

7

ES7 cách năm 2017.

  <script>
  var funcs = [
    _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("7"), 1000))
  ];
  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }
  </script>
  <button onClick="runPromisesInSequence(funcs)">Do the thing</button>

Điều này sẽ thực thi các chức năng đã cho một cách tuần tự (từng cái một), không song song. Tham số promiseslà một mảng các hàm trả về Promise.

Ví dụ về Plunker với đoạn mã trên: http://plnkr.co/edit/UP0rhD?p=preview


4

Lần thử thứ hai với một câu trả lời mà tôi cố gắng giải thích rõ hơn:

Đầu tiên, một số thông tin cơ bản cần thiết, từ RSVP README :

Phần thực sự tuyệt vời đến khi bạn trả lại một lời hứa từ trình xử lý đầu tiên ... Điều này cho phép bạn làm phẳng các lệnh gọi lại lồng nhau và là tính năng chính của các lời hứa ngăn chặn "lệch phải" trong các chương trình có nhiều mã không đồng bộ.

Đây chính là cách bạn thực hiện các lời hứa một cách tuần tự, bằng cách trả lại lời hứa sau đó từ thenlời hứa sẽ kết thúc trước nó.

Sẽ rất hữu ích khi nghĩ về một tập hợp các lời hứa như một cái cây, trong đó các nhánh biểu thị các quá trình tuần tự và các lá biểu thị các quá trình đồng thời.

Quá trình xây dựng một cây hứa hẹn tương tự như nhiệm vụ rất phổ biến của việc xây dựng các loại cây khác: duy trì một con trỏ hoặc tham chiếu đến vị trí trong cây mà bạn hiện đang thêm các nhánh và lặp đi lặp lại thêm các thứ.

Như @Esailija đã chỉ ra trong câu trả lời của mình, nếu bạn có một mảng các hàm trả về lời hứa không nhận đối số, bạn có thể sử dụng reduceđể xây dựng cây một cách gọn gàng cho mình. Nếu bạn đã từng thực hiện giảm cho chính mình, bạn sẽ hiểu rằng những gì giảm đang làm đằng sau hậu trường trong câu trả lời của @ Esailija là duy trì một tham chiếu đến lời hứa hiện tại ( cur) và mỗi lời hứa trả lại lời hứa tiếp theo trong nó then.

Nếu bạn KHÔNG có một mảng thuần nhất đẹp đẽ (đối với các đối số mà chúng lấy / trả về) các hàm trả về hứa hẹn hoặc nếu bạn cần một cấu trúc phức tạp hơn một chuỗi tuyến tính đơn giản, bạn có thể tự xây dựng cây các hứa hẹn bằng cách duy trì tham chiếu đến vị trí trong cây hứa hẹn nơi bạn muốn thêm các lời hứa mới:

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

Bạn có thể xây dựng kết hợp các quy trình đồng thời và tuần tự bằng cách sử dụng RSVP.all để thêm nhiều "lá" vào một "nhánh" lời hứa. Câu trả lời không-vì-quá-phức tạp của tôi cho thấy một ví dụ về điều đó.

Bạn cũng có thể sử dụng Ember.run.scheduleOnce ('afterRender') để đảm bảo rằng điều gì đó được thực hiện trong một lời hứa sẽ được hiển thị trước khi lời hứa tiếp theo được thực hiện - câu trả lời quá phức tạp của tôi cũng cho thấy một ví dụ về điều đó.


3
Điều này tốt hơn rất nhiều, tuy nhiên tôi cảm thấy như bạn vẫn đang lạc đề. Điều này phổ biến đối với nhiều câu trả lời về lời hứa, mọi người dường như không dành thời gian để đọc câu hỏi, thay vào đó họ chỉ nhận xét về một số khía cạnh của lời hứa mà cá nhân họ hiểu. Câu hỏi ban đầu không liên quan đến thực hiện song song, thậm chí không một chút nào, và nó cho thấy rõ ràng rằng chỉ cần xâu chuỗi qua thenlà mong muốn, bạn đã cung cấp rất nhiều thông tin bổ sung ẩn câu trả lời cho câu hỏi đã được hỏi.
David McMullin

@DavidMcMullin ".... và nó cho thấy rõ ràng rằng chỉ cần xâu chuỗi thông qua sau đó là mong muốn ..." nhưng thực ra anh ấy nói rằng chuỗi các lời hứa được xây dựng động. Vì vậy, anh ta cần phải hiểu cách xây dựng một cây, ngay cả khi trong trường hợp này, nó là tập con đơn giản của "chuỗi tuyến tính" của cây. Bạn vẫn phải xây dựng nó bằng cách duy trì một tham chiếu đến lời hứa cuối cùng trong chuỗi và thêm những lời hứa mới vào nó.
Michael Johnston

Khi OP nói rằng "số lượng lời hứa thay đổi và mảng lời hứa được xây dựng động", tôi khá chắc chắn rằng tất cả ý của anh ấy / anh ấy là kích thước của mảng không được xác định trước và do đó anh ấy / anh ấy không thể sử dụng một Promise.resolve().then(...).then(...)..., không phải là mảng đang phát triển trong khi các lời hứa đang thực thi. Tất nhiên, bây giờ là tất cả các cuộc tranh luận.
JLRishe

4

Tuy nhiên, một cách tiếp cận khác là xác định một hàm chuỗi toàn cục trên Promisenguyên mẫu.

Promise.prototype.sequence = async (promiseFns) => {
  for (let promiseFn of promiseFns) {
    await promiseFn();
  }
}

Sau đó, bạn có thể sử dụng nó ở bất cứ đâu, giống như Promise.all()

Thí dụ

const timeout = async ms => new Promise(resolve =>
  setTimeout(() => {
    console.log("done", ms);
    resolve();
  }, ms)
);

// Executed one after the other
await Promise.sequence([() => timeout(1000), () => timeout(500)]);
// done: 1000
// done: 500

// Executed in parallel
await Promise.all([timeout(1000), timeout(500)]);
// done: 500
// done: 1000

Tuyên bố từ chối trách nhiệm: Hãy cẩn thận chỉnh sửa Nguyên mẫu!


2

Tất cả những gì cần thiết để giải quyết đó là một forvòng lặp :)

var promises = [a,b,c];
var chain;

for(let i in promises){
  if(chain) chain = chain.then(promises[i]);
  if(!chain) chain = promises[i]();
}

function a(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve A');
      resolve();
    },1000);
  });
}
function b(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve B');
      resolve();
    },500);
  });
}
function c(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve C');
      resolve();
    },100);
  });
}

Tại sao if(!chain) chain = promises[i]();có một ()ở cuối? Tôi nghĩ rằng trong trường hợp chuỗi trống (lần lặp 0), người ta sẽ chỉ muốn có lời hứa thô và sau đó vòng lặp có thể đưa từng lời hứa tiếp theo vào chuỗi .then(). Vì vậy, điều này sẽ không if(!chain) chain = promises[i];? Có lẽ tôi chưa hiểu điều gì đó ở đây.
halfer

Ah - của bạn a,b,cthực sự là các chức năng trả về các Lời hứa chứ không phải các Lời hứa. Vì vậy, ở trên có ý nghĩa. Nhưng có ích gì khi gói Lời hứa theo cách này?
halfer

2

Tôi đã gặp sự cố tương tự và tôi đã tạo một hàm đệ quy chạy các hàm một cách tuần tự.

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function() {
      return executeSequentially(tasks);
    });
  }

  return Promise.resolve();  
};

Trong trường hợp bạn cần thu thập đầu ra từ các chức năng này:

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function(output) {
      return executeSequentially(tasks).then(function(outputs) {
        outputs.push(output);

        return Promise.resolve(outputs);  
      });
    });
  }

  return Promise.resolve([]);
};

0
export type PromiseFn = () => Promise<any>;

export class PromiseSequence {
  private fns: PromiseFn[] = [];

  push(fn: PromiseFn) {
    this.fns.push(fn)
  }

  async run() {
    for (const fn of this.fns) {
      await fn();
    }
  }
}

sau đó

const seq = new PromiseSequence();
seq.push(() => Promise.resolve(1));
seq.push(() => Promise.resolve(2));
seq.run();

nó cũng có thể lưu trữ những gì hứa hẹn trả về trong một var riêng tư khác và chuyển nó cho các cuộc gọi lại


-1

Thứ tôi theo đuổi về cơ bản là mapSeries, và tôi tình cờ lập bản đồ lưu trên một tập giá trị và tôi muốn kết quả.

Vì vậy, đây là những gì tôi có được, để giúp những người khác tìm kiếm những thứ tương tự trong tương lai ..

(Lưu ý rằng bối cảnh là một ứng dụng Ember).

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

    return Ember.RSVP.all(x);
    }
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.