"Địa ngục gọi lại" là gì và làm thế nào và tại sao RX giải quyết nó?


113

Ai đó có thể đưa ra một định nghĩa rõ ràng cùng với một ví dụ đơn giản giải thích thế nào là "địa ngục gọi lại" cho một người không biết JavaScript và node.js không?

Khi nào (trong loại cài đặt) xảy ra "sự cố địa ngục gọi lại"?

Tại sao nó xảy ra?

Có phải "địa ngục gọi lại" luôn liên quan đến tính toán không đồng bộ không?

Hoặc "địa ngục gọi lại" cũng có thể xảy ra trong một ứng dụng luồng đơn?

Tôi đã tham gia Khóa học phản ứng tại Coursera và Erik Meijer đã nói trong một trong những bài giảng của anh ấy rằng RX giải quyết vấn đề "địa ngục gọi lại". Tôi đã hỏi "địa ngục gọi lại" là gì trên diễn đàn Coursera nhưng tôi không có câu trả lời rõ ràng.

Sau khi giải thích "địa ngục gọi lại" trên một ví dụ đơn giản, bạn cũng có thể chỉ ra cách RX giải quyết "vấn đề địa ngục gọi lại" trên ví dụ đơn giản đó không?

Câu trả lời:


136

1) "Địa ngục gọi lại" đối với người không biết javascript và node.js là gì?

Câu hỏi khác này có một số ví dụ về địa ngục gọi lại Javascript: Cách tránh lồng lâu các hàm không đồng bộ trong Node.js

Vấn đề trong Javascript là cách duy nhất để "đóng băng" một phép tính và để "phần còn lại của nó" thực thi sau (không đồng bộ) là đặt "phần còn lại của nó" bên trong một lệnh gọi lại.

Ví dụ: giả sử tôi muốn chạy mã giống như sau:

x = getData();
y = getMoreData(x);
z = getMoreData(y);
...

Điều gì sẽ xảy ra nếu bây giờ tôi muốn làm cho các hàm getData không đồng bộ, nghĩa là tôi có cơ hội chạy một số mã khác trong khi chờ chúng trả về giá trị của chúng? Trong Javascript, cách duy nhất sẽ là viết lại mọi thứ liên quan đến tính toán không đồng bộ bằng cách sử dụng kiểu truyền tiếp tục :

getData(function(x){
    getMoreData(x, function(y){
        getMoreData(y, function(z){ 
            ...
        });
    });
});

Tôi không nghĩ mình cần thuyết phục bất cứ ai rằng phiên bản này xấu hơn phiên bản trước. :-)

2) Khi nào (trong loại cài đặt) xảy ra "sự cố địa ngục gọi lại"?

Khi bạn có nhiều hàm gọi lại trong mã của mình! Càng khó làm việc với chúng hơn khi bạn có nhiều mã trong mã của mình và nó trở nên đặc biệt tồi tệ khi bạn cần thực hiện các vòng lặp, thử bắt các khối và những thứ tương tự.

Ví dụ, theo như tôi biết, trong JavaScript, cách duy nhất để thực thi một loạt các hàm không đồng bộ trong đó một hàm được chạy sau các lần trả về trước đó là sử dụng một hàm đệ quy. Bạn không thể sử dụng vòng lặp for.

// we would like to write the following
for(var i=0; i<10; i++){
    doSomething(i);
}
blah();

Thay vào đó, chúng ta có thể cần phải viết:

function loop(i, onDone){
    if(i >= 10){
        onDone()
    }else{
        doSomething(i, function(){
            loop(i+1, onDone);
        });
     }
}
loop(0, function(){
    blah();
});

//ugh!

Số lượng câu hỏi mà chúng tôi nhận được ở đây trên StackOverflow hỏi cách thực hiện điều này là một minh chứng cho việc nó khó hiểu như thế nào :)

3) Tại sao nó xảy ra?

Nó xảy ra bởi vì trong JavaScript, cách duy nhất để trì hoãn một tính toán để nó chạy sau khi trả về cuộc gọi không đồng bộ là đặt mã bị trì hoãn bên trong một hàm gọi lại. Bạn không thể trì hoãn mã được viết theo kiểu đồng bộ truyền thống để bạn kết thúc với các lệnh gọi lại lồng nhau ở khắp mọi nơi.

4) Hoặc "địa ngục gọi lại" cũng có thể xảy ra trong một ứng dụng luồng đơn?

Lập trình không đồng bộ liên quan đến đồng thời trong khi một luồng đơn liên quan đến song song. Hai khái niệm thực sự không giống nhau.

Bạn vẫn có thể có mã đồng thời trong một ngữ cảnh luồng đơn. Trên thực tế, JavaScript, nữ hoàng của địa ngục gọi lại, là một chuỗi đơn.

Sự khác biệt giữa đồng thời và song song là gì?

5) bạn cũng có thể vui lòng chỉ ra cách RX giải quyết "vấn đề địa ngục gọi lại" trên ví dụ đơn giản đó.

Tôi không biết gì cụ thể về RX, nhưng thông thường vấn đề này được giải quyết bằng cách thêm hỗ trợ gốc cho tính toán không đồng bộ trong ngôn ngữ lập trình. Các triển khai có thể khác nhau và bao gồm: async, các trình tạo, các ứng dụng điều chỉnh và callcc.

Trong Python, chúng ta có thể triển khai ví dụ vòng lặp trước đó với một cái gì đó dọc theo dòng:

def myLoop():
    for i in range(10):
        doSomething(i)
        yield

myGen = myLoop()

Đây không phải là mã đầy đủ nhưng ý tưởng là "lợi nhuận" tạm dừng vòng lặp for của chúng tôi cho đến khi ai đó gọi myGen.next (). Điều quan trọng là chúng ta vẫn có thể viết mã bằng vòng lặp for, mà không cần chuyển ra logic "từ trong ra ngoài" như chúng ta phải làm trong loophàm đệ quy đó .


Vì vậy, địa ngục gọi lại chỉ có thể xảy ra trong cài đặt không đồng bộ? Nếu mã của tôi là đồng bộ hoàn toàn (tức là không có đồng thời) thì "địa ngục gọi lại" không thể xảy ra nếu tôi hiểu câu trả lời của bạn một cách chính xác, có đúng không?
jhegedus

Địa ngục gọi lại liên quan nhiều hơn đến mức độ khó chịu khi viết mã bằng kiểu truyền tiếp tục. Về mặt lý thuyết, bạn vẫn có thể viết lại tất cả các hàm của mình bằng cách sử dụng kiểu CPS ngay cả đối với một chương trình thông thường (bài báo wikipedia có một số ví dụ) nhưng, vì lý do chính đáng, hầu hết mọi người không làm như vậy. Thông thường chúng ta chỉ sử dụng kiểu truyền tiếp tục nếu chúng ta buộc phải làm như vậy, đây là trường hợp của lập trình không đồng bộ Javascript.
ômomg

btw, tôi đã tìm kiếm các tiện ích mở rộng phản ứng và tôi có ấn tượng rằng chúng giống với thư viện Promise hơn chứ không phải tiện ích mở rộng ngôn ngữ giới thiệu cú pháp không đồng bộ. Các hứa hẹn giúp xử lý lồng gọi lại và xử lý ngoại lệ nhưng chúng không gọn gàng như các phần mở rộng cú pháp. Vòng lặp for vẫn gây khó chịu cho mã và bạn vẫn cần dịch mã từ kiểu đồng bộ sang kiểu hứa.
ômomg

1
Tôi nên làm rõ cách RX nói chung hoạt động tốt hơn. RX là khai báo. Bạn có thể khai báo cách chương trình sẽ phản hồi các sự kiện khi chúng xảy ra sau này mà không ảnh hưởng đến bất kỳ logic nào khác của chương trình. Điều này cho phép bạn tách mã vòng lặp chính khỏi mã xử lý sự kiện. Bạn có thể dễ dàng xử lý các chi tiết như thứ tự sự kiện không đồng bộ là một cơn ác mộng khi sử dụng các biến trạng thái. Tôi thấy RX là cách triển khai rõ ràng nhất để thực hiện một yêu cầu mạng mới sau khi 3 phản hồi mạng được trả lại hoặc để xử lý lỗi trong toàn bộ chuỗi nếu một phản hồi không trở lại. Sau đó, nó có thể tự thiết lập lại và chờ 3 sự kiện giống nhau.
colintheshots

Thêm một nhận xét liên quan: RX về cơ bản là đơn nguyên tiếp tục, liên quan đến CPS nếu tôi không nhầm, điều này cũng có thể giải thích tại sao RX lại tốt cho vấn đề callback / hell.
jhegedus

30

Chỉ cần trả lời câu hỏi: bạn có thể vui lòng chỉ ra cách RX giải quyết "vấn đề địa ngục gọi lại" trên ví dụ đơn giản đó không?

Điều kỳ diệu là flatMap. Chúng ta có thể viết mã sau trong Rx cho ví dụ của @ ômomg:

def getData() = Observable[X]
getData().flatMap(x -> Observable[Y])
         .flatMap(y -> Observable[Z])
         .map(z -> ...)...

Nó giống như bạn đang viết một số mã FP đồng bộ, nhưng thực ra bạn có thể làm cho chúng không đồng bộ bằng cách Scheduler.


26

Để giải quyết câu hỏi về cách Rx giải quyết địa ngục gọi lại :

Đầu tiên, hãy mô tả lại địa ngục gọi lại.

Hãy tưởng tượng một trường hợp là chúng ta phải thực hiện http để có được ba tài nguyên - con người, hành tinh và thiên hà. Mục tiêu của chúng ta là tìm ra thiên hà mà người đó sinh sống. Đầu tiên chúng ta phải tìm được người, sau đó là hành tinh, sau đó là thiên hà. Đó là ba lệnh gọi lại cho ba hoạt động không đồng bộ.

getPerson(person => { 
   getPlanet(person, (planet) => {
       getGalaxy(planet, (galaxy) => {
           console.log(galaxy);
       });
   });
});

Mỗi cuộc gọi lại được lồng vào nhau. Mỗi lệnh gọi lại bên trong phụ thuộc vào cha mẹ của nó. Điều này dẫn đến địa ngục gọi lại theo phong cách "kim tự tháp của sự diệt vong" . Mã trông giống như một dấu>.

Để giải quyết vấn đề này trong RxJs, bạn có thể làm như sau:

getPerson()
  .map(person => getPlanet(person))
  .map(planet => getGalaxy(planet))
  .mergeAll()
  .subscribe(galaxy => console.log(galaxy));

Với toán tử mergeMapAKA, flatMapbạn có thể làm cho nó ngắn gọn hơn:

getPerson()
  .mergeMap(person => getPlanet(person))
  .mergeMap(planet => getGalaxy(planet))
  .subscribe(galaxy => console.log(galaxy));

Như bạn có thể thấy, mã được làm phẳng và chứa một chuỗi các lệnh gọi phương thức. Chúng ta không có "kim tự tháp diệt vong".

Do đó, địa ngục gọi lại được tránh.

Trong trường hợp bạn đang thắc mắc, các lời hứa là một cách khác để tránh gọi lại địa ngục, nhưng các lời hứa là háo hức , không lười biếng như những người quan sát được và (nói chung) bạn không thể hủy chúng dễ dàng.


Tôi không phải là nhà phát triển JS, nhưng đây là lời giải thích dễ hiểu
Omar Beshary

15

Địa ngục gọi lại là bất kỳ mã nào mà việc sử dụng các hàm gọi lại trong mã không đồng bộ trở nên khó hiểu hoặc khó theo dõi. Nói chung, khi có nhiều hơn một cấp hướng dẫn, mã sử dụng lệnh gọi lại có thể trở nên khó theo dõi hơn, khó tái cấu trúc và khó kiểm tra hơn. Một mùi mã là nhiều mức độ thụt lề do chuyển nhiều lớp chữ hàm.

Điều này thường xảy ra khi hành vi có các phụ thuộc, tức là khi A phải xảy ra trước B phải xảy ra trước C. Sau đó, bạn nhận được mã như thế này:

a({
    parameter : someParameter,
    callback : function() {
        b({
             parameter : someOtherParameter,
             callback : function({
                 c(yetAnotherParameter)
        })
    }
});

Nếu bạn có nhiều hành vi phụ thuộc vào mã của mình như thế này, nó có thể trở nên rắc rối nhanh chóng. Đặc biệt nếu nó phân nhánh ...

a({
    parameter : someParameter,
    callback : function(status) {
        if (status == states.SUCCESS) {
          b(function(status) {
              if (status == states.SUCCESS) {
                 c(function(status){
                     if (status == states.SUCCESS) {
                         // Not an exaggeration. I have seen
                         // code that looks like this regularly.
                     }
                 });
              }
          });
        } elseif (status == states.PENDING {
          ...
        }
    }
});

Điều này sẽ không làm được. Làm thế nào chúng ta có thể làm cho mã không đồng bộ thực thi theo một thứ tự xác định mà không cần phải chuyển tất cả các lệnh gọi lại này?

RX là viết tắt của 'phần mở rộng phản ứng'. Tôi chưa sử dụng nó, nhưng Googling gợi ý rằng đó là một khuôn khổ dựa trên sự kiện, điều này có ý nghĩa. Sự kiện là một mô hình chung để làm cho mã thực thi theo thứ tự mà không tạo ra khớp nối giòn . Bạn có thể yêu cầu C lắng nghe sự kiện 'bFinishing' chỉ xảy ra sau khi B được gọi là nghe 'aFinishing'. Sau đó, bạn có thể dễ dàng thêm các bước bổ sung hoặc mở rộng loại hành vi này và có thể dễ dàng kiểm tra xem mã của bạn có thực thi theo thứ tự hay không bằng cách chỉ phát các sự kiện trong trường hợp thử nghiệm của bạn.


1

Gọi lại địa ngục có nghĩa là bạn đang ở trong một cuộc gọi lại bên trong một cuộc gọi lại khác và nó sẽ chuyển sang cuộc gọi thứ n cho đến khi nhu cầu của bạn không được đáp ứng đầy đủ.

Chúng ta hãy hiểu một ví dụ về lệnh gọi ajax giả bằng cách sử dụng API thời gian chờ đã đặt, giả sử chúng ta có API công thức, chúng tôi cần tải xuống tất cả công thức.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Trong ví dụ trên, sau 1,5 giây khi bộ đếm thời gian hết hạn, mã lệnh gọi lại sẽ thực thi, nói cách khác, thông qua lệnh gọi ajax giả của chúng tôi, tất cả công thức sẽ được tải xuống từ máy chủ. Bây giờ chúng ta cần tải xuống một dữ liệu công thức cụ thể.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
                setTimeout(id=>{
                    const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                    console.log(`${id}: ${recipe.title}`);
                }, 1500, recipeId[2])
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Để tải xuống một dữ liệu công thức cụ thể, chúng tôi đã viết mã bên trong lệnh gọi lại đầu tiên và chuyển Id công thức.

Bây giờ, giả sử chúng ta cần tải xuống tất cả các công thức nấu ăn của cùng một nhà xuất bản công thức có id là 7638.

<body>
    <script>
        function getRecipe(){
            setTimeout(()=>{
                const recipeId = [83938, 73838, 7638];
                console.log(recipeId);
                setTimeout(id=>{
                    const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                    console.log(`${id}: ${recipe.title}`);
                    setTimeout(publisher=>{
                        const recipe2 = {title:'Fresh Apple Pie', publisher:'Suru'};
                        console.log(recipe2);
                    }, 1500, recipe.publisher);
                }, 1500, recipeId[2])
            }, 1500);
        }
        getRecipe();
    </script>
</body>

Để đáp ứng đầy đủ nhu cầu của chúng tôi, đó là tải xuống tất cả các công thức nấu ăn của tên nhà xuất bản suru, chúng tôi đã viết mã bên trong cuộc gọi lại thứ hai của chúng tôi. Rõ ràng là chúng tôi đã viết một chuỗi gọi lại được gọi là địa ngục gọi lại.

Nếu bạn muốn tránh gọi lại địa ngục, bạn có thể sử dụng Promise, đây là tính năng js es6, mỗi lời hứa nhận một lệnh gọi lại được gọi khi một lời hứa được điền đầy đủ. Gọi lại lời hứa có hai tùy chọn hoặc nó được giải quyết hoặc từ chối. Giả sử lệnh gọi API của bạn thành công, bạn có thể gọi giải quyết và chuyển dữ liệu qua giải quyết , bạn có thể lấy dữ liệu này bằng cách sử dụng then () . Nhưng nếu API của bạn không thành công, bạn có thể sử dụng từ chối, sử dụng bắt để bắt lỗi. Hãy nhớ rằng một lời hứa luôn sử dụng sau đó để giải quyết và bắt để từ chối

Hãy giải quyết vấn đề địa ngục gọi lại trước đó bằng cách sử dụng một lời hứa.

<body>
    <script>

        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        getIds.then(IDs=>{
            console.log(IDs);
        }).catch(error=>{
            console.log(error);
        });
    </script>
</body>

Bây giờ tải xuống công thức cụ thể:

<body>
    <script>
        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        const getRecipe = recID => {
            return new Promise((resolve, reject)=>{
                setTimeout(id => {
                    const downloadSuccessfull = true;
                    if (downloadSuccessfull){
                        const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                        resolve(`${id}: ${recipe.title}`);
                    }else{
                        reject(`${id}: recipe download failed 404`);
                    }

                }, 1500, recID)
            })
        }
        getIds.then(IDs=>{
            console.log(IDs);
            return getRecipe(IDs[2]);
        }).
        then(recipe =>{
            console.log(recipe);
        })
        .catch(error=>{
            console.log(error);
        });
    </script>
</body>

Bây giờ chúng ta có thể viết một phương thức khác gọi allRecipeOfAPublisher như getRecipe cũng sẽ trả về một lời hứa và chúng ta có thể viết một then () khác để nhận lời hứa quyết định cho allRecipeOfAPublisher, tôi hy vọng tại thời điểm này, bạn có thể tự làm điều đó.

Vì vậy, chúng ta đã học cách xây dựng và sử dụng các lời hứa, bây giờ hãy làm cho việc sử dụng một lời hứa dễ dàng hơn bằng cách sử dụng async / await được giới thiệu trong es8.

<body>
    <script>

        const getIds = new Promise((resolve, reject)=>{
            setTimeout(()=>{
                const downloadSuccessfull = true;
                const recipeId = [83938, 73838, 7638];
                if(downloadSuccessfull){
                    resolve(recipeId);
                }else{
                    reject('download failed 404');
                }
            }, 1500);
        });

        const getRecipe = recID => {
            return new Promise((resolve, reject)=>{
                setTimeout(id => {
                    const downloadSuccessfull = true;
                    if (downloadSuccessfull){
                        const recipe = {title:'Fresh Apple Juice', publisher:'Suru'};
                        resolve(`${id}: ${recipe.title}`);
                    }else{
                        reject(`${id}: recipe download failed 404`);
                    }

                }, 1500, recID)
            })
        }

        async function getRecipesAw(){
            const IDs = await getIds;
            console.log(IDs);
            const recipe = await getRecipe(IDs[2]);
            console.log(recipe);
        }

        getRecipesAw();
    </script>
</body>

Trong ví dụ trên, chúng tôi đã sử dụng một hàm không đồng bộ vì nó sẽ chạy ở chế độ nền, bên trong hàm không đồng bộ, chúng tôi đã sử dụng từ khóa await trước mỗi phương thức trả về hoặc là một lời hứa vì phải đợi ở vị trí đó cho đến khi lời hứa đó được thực hiện, nói cách khác trong mã dưới đây cho đến khi hoàn tất getIds được giải quyết hoặc chương trình từ chối sẽ ngừng thực thi các mã dưới dòng đó khi các ID được trả về sau đó chúng ta lại gọi hàm getRecipe () với một id và đợi bằng cách sử dụng từ khóa await cho đến khi dữ liệu được trả về. Vì vậy, đây là cách cuối cùng chúng tôi đã phục hồi từ địa ngục gọi lại.

  async function getRecipesAw(){
            const IDs = await getIds;
            console.log(IDs);
            const recipe = await getRecipe(IDs[2]);
            console.log(recipe);
        }

Để sử dụng await, chúng ta sẽ cần một hàm async, chúng ta có thể trả về một lời hứa, vì vậy hãy sử dụng then chốt để giải quyết lời hứa và cath để từ chối lời hứa

từ ví dụ trên:

 async function getRecipesAw(){
            const IDs = await getIds;
            const recipe = await getRecipe(IDs[2]);
            return recipe;
        }

        getRecipesAw().then(result=>{
            console.log(result);
        }).catch(error=>{
            console.log(error);
        });

0

Một cách có thể tránh được Callback hell là sử dụng FRP là "phiên bản nâng cao" của RX.

Tôi bắt đầu sử dụng FRP gần đây vì tôi đã tìm thấy một triển khai tốt của nó được gọi là Sodium( http://sodium.nz/ ).

Một mã điển hình trông như thế này (Scala.js):

def render: Unit => VdomElement = { _ =>
  <.div(
    <.hr,
    <.h2("Note Selector"),
    <.hr,
    <.br,
    noteSelectorTable.comp(),
    NoteCreatorWidget().createNewNoteButton.comp(),
    NoteEditorWidget(selectedNote.updates()).comp(),
    <.hr,
    <.br
  )
}

selectedNote.updates()là một Streamkích hoạt nếu selectedNode(là a Cell) thay đổi, NodeEditorWidgetsau đó cập nhật tương ứng.

Vì vậy, tùy thuộc vào nội dung của selectedNode Cell, nội dung hiện đang được chỉnh sửa Notesẽ thay đổi.

Mã này tránh hoàn toàn Callback-s, hầu như, Cacllback-s được đẩy ra "lớp ngoài" / "bề mặt" của ứng dụng, nơi giao diện logic xử lý trạng thái với thế giới bên ngoài. Không có Callbacks cần thiết để truyền dữ liệu trong logic xử lý trạng thái bên trong (thực hiện một máy trạng thái).

Mã nguồn đầy đủ ở đây

Đoạn mã trên tương ứng với ví dụ Tạo / Hiển thị / Cập nhật đơn giản sau:

nhập mô tả hình ảnh ở đây

Mã này cũng gửi các bản cập nhật đến máy chủ, vì vậy các thay đổi đối với các Thực thể được cập nhật sẽ được tự động lưu vào máy chủ.

Tất cả việc xử lý sự kiện được thực hiện bằng cách sử dụng Streams và Cells. Đây là những khái niệm FRP. Các lệnh gọi lại chỉ cần thiết khi logic FRP giao tiếp với thế giới bên ngoài, chẳng hạn như nhập liệu của người dùng, chỉnh sửa văn bản, nhấn nút, lệnh gọi AJAX trả về.

Luồng dữ liệu được mô tả rõ ràng, theo cách khai báo bằng FRP (được thực hiện bởi thư viện Sodium), do đó không cần xử lý sự kiện / logic gọi lại để mô tả luồng dữ liệu.

FRP (là một phiên bản "nghiêm ngặt" hơn của RX) là một cách để mô tả một biểu đồ luồng dữ liệu, có thể chứa các nút có chứa trạng thái. Sự kiện kích hoạt trạng thái thay đổi trong trạng thái chứa các nút (được gọi là Cells).

Natri là một thư viện FRP bậc cao hơn, có nghĩa là sử dụng thư viện flatMap/ switchnguyên thủy có thể sắp xếp lại đồ thị luồng dữ liệu trong thời gian chạy.

Tôi khuyên bạn nên xem qua cuốn sách Sodium , nó giải thích chi tiết cách FRP loại bỏ tất cả các Callback không cần thiết để mô tả logic luồng dữ liệu liên quan đến việc cập nhật trạng thái ứng dụng để phản ứng với một số kích thích bên ngoài.

Sử dụng FRP, chỉ cần giữ lại những Callback mô tả tương tác với thế giới bên ngoài. Nói cách khác, luồng dữ liệu được mô tả theo cách khai báo / chức năng khi một người sử dụng khung FRP (chẳng hạn như Natri) hoặc khi người ta sử dụng khung "FRP giống" (chẳng hạn như RX).

Natri cũng có sẵn cho Javascript / Typescript.


-3

Nếu bạn không có kiến ​​thức về callback và gọi lại hell thì không có vấn đề gì, điều quan trọng nhất là call back và call back hell Ví dụ: hell call back giống như chúng ta có thể lưu trữ một lớp bên trong một lớp. về điều đó được lồng trong ngôn ngữ C, C ++. Nghiệm có nghĩa là một lớp bên trong một lớp khác.


Câu trả lời sẽ hữu ích hơn nếu nó chứa đoạn mã để hiển thị thế nào là 'Địa ngục gọi lại' và cùng một đoạn mã với Rx sau khi loại bỏ 'địa ngục gọi lại'
rafa

-4

Sử dụng jazz.js https://github.com/Javanile/Jazz.js

nó đơn giản hóa như thế này:

    // chạy chuỗi nhiệm vụ tuần tự
    jj.script ([
        // nhiệm vụ đầu tiên
        hàm (tiếp theo) {
            // ở cuối quá trình này 'tiếp theo' trỏ đến tác vụ thứ hai và chạy nó 
            callAsyncProcess1 (tiếp theo);
        },
      // nhiệm vụ thứ hai
      hàm (tiếp theo) {
        // ở cuối quá trình này, 'tiếp theo' trỏ đến tác vụ ngắn và chạy nó 
        callAsyncProcess2 (tiếp theo);
      },
      // nhiệm vụ ngắn hạn
      hàm (tiếp theo) {
        // ở cuối quá trình này 'tiếp theo' trỏ đến (nếu có) 
        callAsyncProcess3 (tiếp theo);
      },
    ]);


hãy xem xét siêu nhỏ gọn như thế này github.com/Javanile/Jazz.js/wiki/Script-showcase
cicciodarkast
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.