Cách chính xác để viết vòng cho lời hứa.


116

Cách xây dựng chính xác một vòng lặp để đảm bảo cuộc gọi hứa hẹn sau đây và logger.log (res) chạy đồng bộ thông qua việc lặp lại? (chim xanh)

db.getUser(email).then(function(res) { logger.log(res); }); // this is a promise

Tôi đã thử cách sau (phương pháp từ http://blog.victorquinn.com/javascript-promise-fter-loop )

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
    var resolver = Promise.defer();

    var loop = function() {
        if (!condition()) return resolver.resolve();
        return Promise.cast(action())
            .then(loop)
            .catch(resolver.reject);
    };

    process.nextTick(loop);

    return resolver.promise;
});

var count = 0;
promiseWhile(function() {
    return count < 10;
}, function() {
    return new Promise(function(resolve, reject) {
        db.getUser(email)
          .then(function(res) { 
              logger.log(res); 
              count++;
              resolve();
          });
    }); 
}).then(function() {
    console.log('all done');
}); 

Mặc dù nó có vẻ hoạt động, nhưng tôi không nghĩ nó đảm bảo thứ tự gọi logger.log (res);

Bất kỳ đề xuất?


1
Mã có vẻ tốt đối với tôi (đệ quy với loophàm là cách thực hiện các vòng lặp đồng bộ). Tại sao bạn nghĩ rằng không có bảo đảm?
hugomg

db.getUser (email) được đảm bảo được gọi theo thứ tự. Nhưng, vì chính db.getUser () là một lời hứa, nên gọi nó theo tuần tự không nhất thiết có nghĩa là các truy vấn cơ sở dữ liệu cho 'email' chạy tuần tự do tính năng không đồng bộ của lời hứa. Do đó, logger.log (res) được gọi tùy thuộc vào truy vấn nào xảy ra để kết thúc trước.
user2127480

1
@ user2127480: Nhưng lần lặp tiếp theo của vòng lặp chỉ được gọi tuần tự sau khi lời hứa đã được giải quyết, đó là cách whilemã đó hoạt động?
Bergi

Câu trả lời:


78

Tôi không nghĩ rằng nó đảm bảo thứ tự gọi logger.log (res);

Trên thực tế, nó làm. Câu lệnh đó được thực thi trước resolvecuộc gọi.

Bất kỳ đề xuất?

Rất nhiều. Điều quan trọng nhất là việc bạn sử dụng antipotype tạo lời hứa bằng tay - chỉ cần làm

promiseWhile(…, function() {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 count++;
             });
})…

Thứ hai, whilechức năng đó có thể được đơn giản hóa rất nhiều:

var promiseWhile = Promise.method(function(condition, action) {
    if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

Thứ ba, tôi sẽ không sử dụng whilevòng lặp (với biến đóng) mà là forvòng lặp:

var promiseFor = Promise.method(function(condition, action, value) {
    if (!condition(value)) return value;
    return action(value).then(promiseFor.bind(null, condition, action));
});

promiseFor(function(count) {
    return count < 10;
}, function(count) {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 return ++count;
             });
}, 0).then(console.log.bind(console, 'all done'));

2
Giáo sư. Ngoại trừ việc actionmất valuenhư là đối số của nó trong promiseFor. Vì vậy, tôi sẽ không để tôi thực hiện một chỉnh sửa nhỏ như vậy. Cảm ơn, nó rất hữu ích và thanh lịch.
Gordon

1
@ Roamer-1888: Có thể thuật ngữ này hơi kỳ lạ, nhưng ý tôi là một whilevòng lặp kiểm tra một số trạng thái toàn cầu trong khi một forvòng lặp có biến lặp (bộ đếm) liên kết với thân vòng lặp. Trong thực tế, tôi đã sử dụng một cách tiếp cận chức năng nhiều hơn trông giống như một lần lặp điểm cố định hơn là một vòng lặp. Kiểm tra mã của họ một lần nữa, valuetham số là khác nhau.
Bergi

2
OK, tôi thấy nó bây giờ. Khi .bind()làm xáo trộn cái mới value, tôi nghĩ rằng tôi có thể chọn sử dụng chức năng này để dễ đọc. Và xin lỗi nếu tôi dày nhưng nếu promiseForpromiseWhilekhông cùng tồn tại thì làm sao người này gọi người kia?
Roamer-1888

2
@herve Về cơ bản bạn có thể bỏ qua nó và thay thế return …bằng return Promise.resolve(…). Nếu bạn cần các biện pháp bảo vệ bổ sung chống lại conditionhoặc actionném một ngoại lệ (như Promise.methodcung cấp nó ), hãy bọc toàn bộ cơ thể chức năng trong mộtreturn Promise.resolve().then(() => { … })
Bergi

2
@herve Trên thực tế điều đó nên Promise.resolve().then(action).…hoặc Promise.resolve(action()).…, bạn không cần phải bọc giá trị trả về củathen
Bergi

134

Nếu bạn thực sự muốn một vị tướng promiseWhen() chức năng cho mục đích này và các mục đích khác, thì bằng mọi cách hãy làm như vậy, sử dụng đơn giản hóa của Bergi. Tuy nhiên, vì cách thức hứa hẹn hoạt động, việc chuyển các cuộc gọi lại theo cách này thường không cần thiết và buộc bạn phải nhảy qua những vòng nhỏ phức tạp.

Theo như tôi có thể nói bạn đang cố gắng:

  • để tìm nạp không đồng bộ một loạt các chi tiết người dùng cho một bộ sưu tập địa chỉ email (ít nhất, đó là kịch bản duy nhất có ý nghĩa).
  • để làm như vậy bằng cách xây dựng một .then()chuỗi thông qua đệ quy.
  • để duy trì thứ tự ban đầu khi xử lý các kết quả trả về.

Do đó, được xác định, vấn đề thực sự là vấn đề được thảo luận trong phần "Bộ sưu tập Kerfuffle" trong các mẫu chống Promise , cung cấp hai giải pháp đơn giản:

  • các cuộc gọi không đồng bộ song song sử dụng Array.prototype.map()
  • cuộc gọi không đồng bộ nối tiếp sử dụng Array.prototype.reduce().

Cách tiếp cận song song sẽ (đơn giản) đưa ra vấn đề mà bạn đang cố gắng tránh - rằng thứ tự của các câu trả lời là không chắc chắn. Cách tiếp cận nối tiếp sẽ xây dựng .then()chuỗi yêu cầu - phẳng - không đệ quy.

function fetchUserDetails(arr) {
    return arr.reduce(function(promise, email) {
        return promise.then(function() {
            return db.getUser(email).done(function(res) {
                logger.log(res);
            });
        });
    }, Promise.resolve());
}

Gọi như sau:

//Compose here, by whatever means, an array of email addresses.
var arrayOfEmailAddys = [...];

fetchUserDetails(arrayOfEmailAddys).then(function() {
    console.log('all done');
});

Như bạn có thể thấy, không cần var bên ngoài xấu xí counthay conditionchức năng liên quan của nó . Giới hạn (của 10 trong câu hỏi) được xác định hoàn toàn bởi độ dài của mảng arrayOfEmailAddys.


16
cảm thấy như thế này nên là câu trả lời được chọn cách tiếp cận duyên dáng và rất tái sử dụng.
ken

1
Có ai biết nếu một đánh bắt sẽ tuyên truyền lại cho cha mẹ? Ví dụ: nếu db.getUser bị lỗi, lỗi (từ chối) sẽ lan truyền sao lưu?
wayofthefuture

@wayofthefuture, không. Hãy nghĩ về nó theo cách này ..... bạn không thể thay đổi lịch sử.
Roamer-1888

4
Cảm ơn câu trả lời. Đây phải là câu trả lời được chấp nhận.
klvs

1
@ Roamer-1888 Sai lầm của tôi, tôi đã đọc sai câu hỏi ban đầu. Tôi (cá nhân) đã xem xét một giải pháp trong đó danh sách nội bộ bạn cần giảm đang tăng lên khi các yêu cầu của bạn được giải quyết (đó là một truy vấnMore of DB). Trong trường hợp này, tôi thấy ý tưởng sử dụng less với một bộ tạo khá tách biệt (1) phần mở rộng có điều kiện của chuỗi hứa hẹn và (2) mức tiêu thụ của các resuls được trả lại.
jhp

40

Đây là cách tôi làm với đối tượng Promise tiêu chuẩn.

// Given async function sayHi
function sayHi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Hi');
      resolve();
    }, 3000);
  });
}

// And an array of async functions to loop through
const asyncArray = [sayHi, sayHi, sayHi];

// We create the start of a promise chain
let chain = Promise.resolve();

// And append each function in the array to the promise chain
for (const func of asyncArray) {
  chain = chain.then(func);
}

// Output:
// Hi
// Hi (After 3 seconds)
// Hi (After 3 more seconds)

Câu trả lời tuyệt vời @youngwerth
Jam Risser

3
Làm thế nào để gửi params theo cách này?
Akash khan

4
@khan trên dòng chain = chain.then (func), bạn có thể thực hiện: chain = chain.then(func.bind(null, "...your params here")); hoặc chain = chain.then(() => func("your params here"));
youngwerth

9

Được

  • chức năng asyncFn
  • mảng vật phẩm

Cần thiết

  • hứa hẹn xâu chuỗi .then () trong chuỗi (theo thứ tự)
  • es6 bản địa

Giải pháp

let asyncFn = (item) => {
  return new Promise((resolve, reject) => {
    setTimeout( () => {console.log(item); resolve(true)}, 1000 )
  })
}

// asyncFn('a')
// .then(()=>{return async('b')})
// .then(()=>{return async('c')})
// .then(()=>{return async('d')})

let a = ['a','b','c','d']

a.reduce((previous, current, index, array) => {
  return previous                                    // initiates the promise chain
  .then(()=>{return asyncFn(array[index])})      //adds .then() promise for each item
}, Promise.resolve())

2
Nếu asyncsắp trở thành một từ dành riêng trong JavaScript, nó có thể thêm sự rõ ràng để đổi tên hàm đó ở đây.
hà mã

Ngoài ra, không phải là trường hợp mũi tên béo hoạt động mà không có cơ thể trong niềng răng chỉ đơn giản trả lại những gì biểu hiện ở đó đánh giá? Điều đó sẽ làm cho mã ngắn gọn hơn. Tôi cũng có thể thêm một bình luận nói rằng currentkhông được sử dụng.
hà mã

2
đây là cách thích hợp
teleme.io

4

Có một cách mới để giải quyết vấn đề này và đó là sử dụng async / await.

async function myFunction() {
  while(/* my condition */) {
    const res = await db.getUser(email);
    logger.log(res);
  }
}

myFunction().then(() => {
  /* do other stuff */
})

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_feft https://ponyfoo.com/articles/under Hiểu-javascript-async-randit


Cảm ơn bạn, điều này không liên quan đến việc sử dụng một khung (bluebird).
Rolf

3

Chức năng gợi ý của Bergi's thực sự rất hay:

var promiseWhile = Promise.method(function(condition, action) {
      if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

Tôi vẫn muốn thực hiện một bổ sung nhỏ, có ý nghĩa khi sử dụng lời hứa:

var promiseWhile = Promise.method(function(condition, action, lastValue) {
  if (!condition()) return lastValue;
  return action().then(promiseWhile.bind(null, condition, action));
});

Bằng cách này, vòng lặp while có thể được nhúng vào chuỗi hứa hẹn và giải quyết bằng lastValue (cũng như nếu hành động () không bao giờ chạy). Xem ví dụ:

var count = 10;
util.promiseWhile(
  function condition() {
    return count > 0;
  },
  function action() {
    return new Promise(function(resolve, reject) {
      count = count - 1;
      resolve(count)
    })
  },
  count)

3

Tôi sẽ làm một cái gì đó như thế này:

var request = []
while(count<10){
   request.push(db.getUser(email).then(function(res) { return res; }));
   count++
};

Promise.all(request).then((dataAll)=>{
  for (var i = 0; i < dataAll.length; i++) {

      logger.log(dataAll[i]); 
  }  
});

theo cách này, dataAll là một mảng được sắp xếp của tất cả các phần tử để đăng nhập. Và hoạt động đăng nhập sẽ thực hiện khi tất cả các lời hứa được thực hiện.


Promise.all sẽ gọi lời hứa sẽ gọi cùng một lúc. Vì vậy, thứ tự hoàn thành có thể thay đổi. Câu hỏi yêu cầu những lời hứa bị xiềng xích. Vì vậy, thứ tự hoàn thành không nên thay đổi.
canbax

Chỉnh sửa 1: Bạn không cần phải gọi Promise.all cả. Chừng nào những lời hứa được thực hiện, chúng sẽ được thực hiện song song.
canbax

1

Sử dụng async và chờ đợi (es6):

function taskAsync(paramets){
 return new Promise((reslove,reject)=>{
 //your logic after reslove(respoce) or reject(error)
})
}

async function fName(){
let arry=['list of items'];
  for(var i=0;i<arry.length;i++){
   let result=await(taskAsync('parameters'));
}

}

0
function promiseLoop(promiseFunc, paramsGetter, conditionChecker, eachFunc, delay) {
    function callNext() {
        return promiseFunc.apply(null, paramsGetter())
            .then(eachFunc)
    }

    function loop(promise, fn) {
        if (delay) {
            return new Promise(function(resolve) {
                setTimeout(function() {
                    resolve();
                }, delay);
            })
                .then(function() {
                    return promise
                        .then(fn)
                        .then(function(condition) {
                            if (!condition) {
                                return true;
                            }
                            return loop(callNext(), fn)
                        })
                });
        }
        return promise
            .then(fn)
            .then(function(condition) {
                if (!condition) {
                    return true;
                }
                return loop(callNext(), fn)
            })
    }

    return loop(callNext(), conditionChecker);
}


function makeRequest(param) {
    return new Promise(function(resolve, reject) {
        var req = https.request(function(res) {
            var data = '';
            res.on('data', function (chunk) {
                data += chunk;
            });
            res.on('end', function () {
                resolve(data);
            });
        });
        req.on('error', function(e) {
            reject(e);
        });
        req.write(param);
        req.end();
    })
}

function getSomething() {
    var param = 0;

    var limit = 10;

    var results = [];

    function paramGetter() {
        return [param];
    }
    function conditionChecker() {
        return param <= limit;
    }
    function callback(result) {
        results.push(result);
        param++;
    }

    return promiseLoop(makeRequest, paramGetter, conditionChecker, callback)
        .then(function() {
            return results;
        });
}

getSomething().then(function(res) {
    console.log('results', res);
}).catch(function(err) {
    console.log('some error along the way', err);
});

0

Làm thế nào về điều này bằng cách sử dụng BlueBird ?

function fetchUserDetails(arr) {
    return Promise.each(arr, function(email) {
        return db.getUser(email).done(function(res) {
            logger.log(res);
        });
    });
}

0

Đây là một phương pháp khác (ES6 w / std Promise). Sử dụng tiêu chí thoát kiểu lodash / gạch dưới (return === false). Lưu ý rằng bạn có thể dễ dàng thêm phương thức exit If () trong các tùy chọn để chạy trong doOne ().

const whilePromise = (fnReturningPromise,options = {}) => { 
    // loop until fnReturningPromise() === false
    // options.delay - setTimeout ms (set to 0 for 1 tick to make non-blocking)
    return new Promise((resolve,reject) => {
        const doOne = () => {
            fnReturningPromise()
            .then((...args) => {
                if (args.length && args[0] === false) {
                    resolve(...args);
                } else {
                    iterate();
                }
            })
        };
        const iterate = () => {
            if (options.delay !== undefined) {
                setTimeout(doOne,options.delay);
            } else {
                doOne();
            }
        }
        Promise.resolve()
        .then(iterate)
        .catch(reject)
    })
};

0

Sử dụng đối tượng lời hứa tiêu chuẩn và có lời hứa sẽ trả về kết quả.

function promiseMap (data, f) {
  const reducer = (promise, x) =>
    promise.then(acc => f(x).then(y => acc.push(y) && acc))
  return data.reduce(reducer, Promise.resolve([]))
}

var emails = []

function getUser(email) {
  return db.getUser(email)
}

promiseMap(emails, getUser).then(emails => {
  console.log(emails)
})

0

Đầu tiên hãy lấy mảng lời hứa (mảng hứa) và sau khi giải quyết các mảng lời hứa này bằng cách sử dụng Promise.all(promisearray).

var arry=['raju','ram','abdul','kruthika'];

var promiseArry=[];
for(var i=0;i<arry.length;i++) {
  promiseArry.push(dbFechFun(arry[i]));
}

Promise.all(promiseArry)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
     console.log(error);
  });

function dbFetchFun(name) {
  // we need to return a  promise
  return db.find({name:name}); // any db operation we can write hear
}
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.