Cách xử lý nhiều sự chờ đợi trong chức năng async


8

Tôi có nhiều lệnh gọi API được thực hiện để tìm nạp thông qua API, ghi dữ liệu vào DB qua API, gửi đầu ra đến giao diện người dùng thông qua API khác.

Tôi đã viết chức năng async với sự chờ đợi như bên dưới -

Đầu tiên 2 nên chạy cái khác nhưng cái thứ ba có thể chạy độc lập và không cần đợi 2 câu lệnh tìm nạp đầu tiên hoàn thành.

let getToken= await fetch(url_for_getToken);
let getTokenData = await getToken.json();

let writeToDB = await fetch(url_for_writeToDB);
let writeToDBData = await writeToDB.json();

let frontEnd = await fetch(url_for_frontEnd);
let frontEndData = await frontEnd.json();

Cách tốt nhất để xử lý nhiều câu lệnh tìm nạp như vậy là gì?


6
Bạn có thể có một cái nhìn tại Promise.all .
Yannick K

2
@YannickK Sẽ hứa. Có cần thiết ở đây không? Anh ta không thể sử dụng .then () chứ? Anh ấy không chờ đợi sự hoàn thành của cả hai, mà là thứ nhất sau đó thứ hai, sau đó thứ ba không phân biệt hai thứ đó.
Kobe

1
@Kobe Tôi nghĩ vấn đề chính trong trường hợp này là OP muốn tách các máy chủ và máy khách gọi vì chúng tôi không phụ thuộc vào nhau - và nó sẽ là ngớ ngẩn hiệu suất-khôn ngoan nếu họ chờ đợi vào nhau - nhưng nếu bất kỳ của họ thất bại bạn muốn từ chối. Bạn chắc chắn đúng rằng anh ta có thể làm mà không cần Promise.all, nhưng trong trường hợp này tôi sẽ tưởng tượng nó sẽ sạch hơn (và dễ dàng xây dựng hơn trong tương lai) nếu anh ta gói gọn mọi thứ trong một Promise.allcuộc gọi, đặc biệt là xử lý lỗi.
Yannick K

@Kobe Bởi vì Promise.allnó rất cần thiết để xử lý lỗi thích hợp và chờ hoàn thành cả hai lời hứa thứ nhất và thứ hai và thứ ba.
Bergi

1
Các câu trả lời đơn giản nhất giải quyết vấn đề tốt nhất, nhưng tiếc là không đáng downvoted. Thật đáng để cho nó một phát súng, @Yasar Abdulllah.
AndreasPizsa

Câu trả lời:


2

Có nhiều cách nhưng phổ biến nhất là bọc từng đường dẫn mã async trong hàm async. Điều này cho phép bạn linh hoạt kết hợp các giá trị trả về async như bạn muốn. Trong ví dụ của bạn, bạn thậm chí có thể mã nội tuyến với async của iife:

await Promise.all([
  (async() => {
    let getToken = await fetch(url_for_getToken);
    let getTokenData = await getToken.json();

    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
  })(),
  (async() => {
    let frontEnd = await fetch(url_for_frontEnd);
    let frontEndData = await frontEnd.json();
  })()
]);

Cuối cùng, một câu trả lời thích hợp! : D
Bergi

1

Bạn có thể sử dụng .then(), thay vì chờ đợi:

fetch(url_for_getToken)
  .then(getToken => getToken.json())
  .then(async getTokenData => {
    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
    // Carry on
  })

fetch(url_for_frontEnd)
  .then(frontEnd => frontEnd.json())
  .then(frontEndData => {
    // Carry on  
  })

1
Đừng quên xử lý lỗi trên các chuỗi lời hứa đó - hoặc Promise.allchúng và trả lại cho người gọi.
Bergi

1

Sẽ dễ dàng hơn nếu bạn làm việc với lời hứa "người tạo" (= chức năng trả lại lời hứa) thay vì lời hứa thô. Đầu tiên, xác định:

const fetchJson = (url, opts) => () => fetch(url, opts).then(r => r.json())

trong đó trả về một "tác giả" như vậy. Bây giờ, đây là hai tiện ích cho chuỗi nối tiếp và song song, chấp nhận cả lời hứa thô và "người tạo":

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => Promise.all(fns.map(call));

async function series(...fns) {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
}

Sau đó, mã chính có thể được viết như thế này:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        fetchJson(url_for_getToken),
        fetchJson(url_for_writeToDB),
    ),
    fetchJson(url_for_frontEnd),
)

Nếu bạn không thích trình bao bọc "người tạo" chuyên dụng, bạn có thể xác định fetchJsonbình thường

const fetchJson = (url, opts) => fetch(url, opts).then(r => r.json())

và sử dụng các phần tiếp theo nội tuyến ngay tại nơi serieshoặc parallelđược gọi là:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        () => fetchJson('getToken'),
        () => fetchJson('writeToDB'),
    ),
    () => fetchJson('frontEnd'), // continuation not necessary, but looks nicer
)

Để đưa ý tưởng đi xa hơn, chúng ta có thể thực hiện seriesparalleltrả lại "người sáng tạo" thay vì hứa hẹn. Bằng cách này, chúng ta có thể xây dựng các "mạch" lồng nhau tùy ý của các lời hứa nối tiếp và song song và nhận được kết quả theo thứ tự. Hoàn thành ví dụ làm việc:

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => () => Promise.all(fns.map(call));

const series = (...fns) => async () => {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
};

//

const request = (x, time) => () => new Promise(resolve => {
    console.log('start', x);
    setTimeout(() => {
        console.log('end', x)
        resolve(x)
    }, time)
});

async function main() {
    let chain = series(
        parallel(
            series(
                request('A1', 500),
                request('A2', 200),
            ),
            series(
                request('B1', 900),
                request('B2', 400),
                request('B3', 400),
            ),
        ),
        parallel(
            request('C1', 800),
            series(
                request('C2', 100),
                request('C3', 100),
            )
        ),
    );

    let results = await chain();

    console.log(JSON.stringify(results))
}

main()
.as-console-wrapper { max-height: 100% !important; top: 0; }


Tại sao bạn không trả lại lời hứa fetchJson? Tôi không thể thấy bất kỳ lợi ích nào từ việc gói lời hứa với chức năng khác. Và tính hữu dụng của seriescó thể nghi ngờ vì hàm tiếp theo trong chuỗi thường yêu cầu giá trị trả về của (các) hàm trước.
marzelin

@marzelin: lời hứa bắt đầu ngay lập tức khi tạo, do đó series(promiseA, promiseB)sẽ bắt đầu ABcùng một lúc.
Georgia

1
à, vâng Tất cả lễ này chỉ để sử dụng series. Tôi thà thích async iife cho các chuỗi và Promise.allSettledthay vì parallel.
marzelin

@marzelin: Tôi tìm thấy cú pháp này sạch hơn (xem cập nhật).
Georgia

1
@ KamilKiełczewski: Tôi đã cập nhật bài đăng ... Ý tưởng là cú pháp tiếp tục này cho phép xây dựng "dòng chảy" lời hứa lồng nhau mà không gặp nhiều rắc rối.
Georgia

-2

Chạy yêu cầu độc lập ( writeToDB) ở đầu và khôngawait

let writeToDB = fetch(url_for_writeToDB);

let getToken = await fetch(url_for_getToken);
let getTokenData = await getToken.json();

// ...


1
@Bergi câu hỏi không phải là về xử lý lỗi - xử lý lỗi (như thử bắt) rất quan trọng nhưng đó là chủ đề riêng biệt
Kamil Kiełczewski

awaitcó xử lý lỗi được tích hợp sẵn - khi lời hứa từ chối, bạn nhận được một ngoại lệ, lời hứa được trả lại bởi async functioncuộc gọi sẽ từ chối và người gọi sẽ thông báo. Nếu bạn đề nghị bỏ awaittừ khóa và chạy chuỗi lời hứa một cách độc lập, bạn không được làm như vậy mà không nghĩ đến việc xử lý lỗi. Một câu trả lời ít nhất không đề cập đến nó (hoặc bảo tồn hành vi ban đầu của người gọi bị từ chối) là một câu trả lời tồi.
Bergi

1
Câu trả lời này giải quyết vấn đề của OP và nó cũng là câu trả lời đơn giản nhất trong số các câu trả lời được trình bày cho đến nay. Tôi tò mò về lý do tại sao điều này đã bị hạ cấp? OP tuyên bố " cái thứ 3 có thể chạy độc lập và không cần phải đợi 2 câu lệnh tìm nạp đầu tiên hoàn thành. ", Vì vậy, thực hiện nó trước là tốt.
AndreasPizsa

@AndreasPizsa Quá đơn giản . Tôi sẽ không đánh giá thấp nó nếu có ít nhất một tuyên bố từ chối rằng các lỗi không được xử lý - ngay khi được đề cập, bất kỳ ai cũng có thể đưa ra ý kiến ​​của riêng mình về việc liệu nó có phù hợp với trường hợp của họ hay không - thậm chí có thể là cho OP . Nhưng bỏ qua điều này dẫn đến các lỗi tồi tệ nhất của tất cả, và làm cho câu trả lời này không hữu ích.
Bergi

1
Cảm ơn đã bình luận @Bergi. Cho rằng câu hỏi của OP cũng không bao gồm xử lý lỗi, tôi sẽ cho rằng nó bị bỏ qua cho ngắn gọn và vượt quá phạm vi của câu hỏi. Nhưng có, bất cứ ai cũng có thể bỏ phiếu với ý kiến ​​riêng của họ.
AndreasPizsa
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.