המתנה באמצעות קריאות חוזרות (callback)

הפונקציה Callback מאפשרת להריץ תהליכי עבודה בהמתנה לשירות אחר שישלח בקשה לנקודת הקצה של ה-callback. הבקשה הזו מפעילה מחדש את תהליך העבודה.

באמצעות קריאות חוזרות (callback), אתם יכולים לציין בתהליך העבודה שאירוע מסוים התרחש, ולהמתין לאירוע הזה בלי לבצע סקר. לדוגמה, אפשר ליצור תהליך עבודה ששולח לכם הודעה כשמוצר חוזר למלאי או כשפריט נשלח, או תהליך עבודה שממתין לאינטראקציה אנושית כמו בדיקת הזמנה או אימות תרגום.

בדף הזה מוסבר איך ליצור תהליך עבודה שתומך בנקודת קצה של קריאה חוזרת (callback), וממתין לבקשות HTTP מתהליכים חיצוניים שיגיעו לנקודת הקצה הזו. אפשר גם לחכות לאירועים באמצעות קריאות חוזרות (callback) וטריגרים של Eventarc.

כדי להשתמש בקריאות חוזרות, צריך להשתמש בשתי פונקציות מובנות של ספרייה רגילה:

  • events.create_callback_endpoint—יוצר נקודת קצה (endpoint) של קריאה חוזרת (callback) שמצפה לשיטת ה-HTTP שצוינה
  • events.await_callback – המתנה לקבלת שיחה חוזרת בנקודת הקצה הנתונה

יצירת נקודת קצה שמקבלת בקשת קריאה חוזרת

יוצרים נקודת קצה של קריאה חוזרת שיכולה לקבל בקשות HTTP שמגיעות לנקודת הקצה הזו.

  1. פועלים לפי השלבים ליצירה של תהליך עבודה חדש, או בוחרים תהליך עבודה קיים כדי לעדכן אותו, אבל לא פורסים אותו עדיין.
  2. בהגדרת תהליך העבודה, מוסיפים שלב ליצירת נקודת קצה של קריאה חוזרת:

    YAML

        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "METHOD"
            result: callback_details
        

    JSON

        [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "METHOD"
              },
              "result": "callback_details"
            }
          }
        ]
          

    מחליפים את METHOD ב-method הצפוי של HTTP, אחד מהערכים GET,‏ HEAD,‏ POST,‏ PUT,‏ DELETE,‏ OPTIONS או PATCH. ערך ברירת המחדל הוא POST.

    התוצאה היא מפה, callback_details, עם שדה url שבו מאוחסנת כתובת ה-URL של נקודת הקצה שנוצרה.

    נקודת הקצה של הקריאה החוזרת מוכנה עכשיו לקבל בקשות נכנסות עם שיטת ה-HTTP שצוינה. אפשר להשתמש בכתובת ה-URL של נקודת הקצה שנוצרה כדי להפעיל את הקריאה החוזרת מתהליך חיצוני לתהליך העבודה. לדוגמה, אפשר להעביר את כתובת ה-URL לפונקציה של Cloud Run.

  3. בהגדרת תהליך העבודה, מוסיפים שלב להמתנה לבקשת שיחה חוזרת:

    YAML

        - await_callback:
            call: events.await_callback
            args:
                callback: ${callback_details}
                timeout: TIMEOUT
            result: callback_request
        

    JSON

        [
          {
            "await_callback": {
              "call": "events.await_callback",
              "args": {
                "callback": "${callback_details}",
                "timeout": TIMEOUT
              },
              "result": "callback_request"
            }
          }
        ]
          

    מחליפים את TIMEOUT במספר השניות המקסימלי שצריך להמתין לבקשה בתהליך העבודה. ברירת המחדל היא 43,200 (12 שעות). אם הזמן חולף לפני קבלת בקשה, מופעלת חריגה מסוג TimeoutError.

    שימו לב שיש משך ביצוע מקסימלי. מידע נוסף זמין במאמר בנושא מגבלת הבקשות.

    המפה callback_details מהשלב הקודם create_callback מועברת כארגומנט.

  4. מפעילים את תהליך העבודה כדי לסיים את היצירה או העדכון שלו.

    כשמתקבלת בקשה, כל הפרטים שלה נשמרים במפה callback_request. לאחר מכן תהיה לכם גישה לבקשת ה-HTTP המלאה, כולל הכותרת, הגוף ומפת query של כל פרמטרים של שאילתה. לדוגמה:

    YAML

        http_request:
          body:
          headers: {...}
          method: GET
          query: {}
          url: "/v1/projects/350446661175/locations/us-central1/workflows/workflow-1/executions/46804f42-dc83-46d6-87e4-93962866ed81/callbacks/49c80102-74d2-49cd-a70e-805a9fded94f_2de9b413-6332-412d-99c3-d7e9b6eeeda2"
        received_time: 2021-06-24 12:49:16.988072651 -0700 PDT m=+742581.005780667
        type: HTTP
        

    JSON

        {
           "http_request":{
              "body":null,
              "headers":{
                 ...
              },
              "method":"GET",
              "query":{
              },
              "url":"/v1/projects/350446661175/locations/us-central1/workflows/workflow-1/executions/46804f42-dc83-46d6-87e4-93962866ed81/callbacks/49c80102-74d2-49cd-a70e-805a9fded94f_2de9b413-6332-412d-99c3-d7e9b6eeeda2"
           },
           "received_time":"2021-06-24 12:49:16.988072651 -0700 PDT m=+742581.005780667",
           "type":"HTTP"
        }
          

    אם גוף ה-HTTP הוא טקסט או JSON, ‏ Workflows ינסה לפענח את הגוף. אחרת, יוחזרו בייטים גולמיים.

אישור בקשות לנקודת הקצה (endpoint) של השיחה החוזרת

כדי לשלוח בקשה לנקודת קצה של קריאה חוזרת, שירותים כמו Cloud Run ופונקציות של Cloud Run, וגם שירותים של צד שלישי, צריכים לקבל הרשאה לכך באמצעות הרשאות מתאימות לניהול זהויות והרשאות גישה (IAM). ההרשאות הספציפיות שנדרשות הן workflows.callbacks.send (כלולות בתפקיד Workflows Invoker). Google Cloud

שליחת בקשה ישירה

הדרך הכי פשוטה ליצור פרטי כניסה לטווח קצר לחשבון שירות היא לשלוח בקשה ישירה. בתהליך הזה מעורבות שתי זהויות: מבצע הקריאה החוזרת וחשבון השירות שעבורו נוצרו פרטי הכניסה. הקריאה אל תהליך העבודה הבסיסי בדף הזה היא דוגמה לבקשה ישירה. מידע נוסף זמין במאמרים שימוש ב-IAM כדי לשלוט בגישה ובקשת הרשאות ישירות.

יצירת אסימון גישה מסוג OAuth 2.0

כדי לתת לאפליקציה הרשאה לקרוא לנקודת הקצה של הקריאה החוזרת, אפשר ליצור אסימון גישה מסוג OAuth 2.0 לחשבון השירות שמשויך לתהליך העבודה. אם יש לכם את ההרשאות הנדרשות (לתפקידים Workflows Editor או Workflows Admin ו-Service Account Token Creator), אתם יכולים גם ליצור אסימון בעצמכם על ידי הרצת השיטה generateAccessToken.

אם הבקשה של generateAccessToken תאושר, גוף התגובה שיוחזר יכלול אסימון גישה מסוג OAuth 2.0 ומועד תפוגה. (כברירת מחדל, אסימוני גישה מסוג OAuth 2.0 תקפים למשך שעה אחת לכל היותר). לדוגמה:

  {
  "accessToken": "eyJ0eXAi...NiJ9",
  "expireTime": "2020-04-07T15:01:23.045123456Z"
  }
אחר כך אפשר להשתמש בקוד accessToken בקריאה ל-URL של נקודת הקצה של הקריאה החוזרת, כמו בדוגמאות הבאות:
  curl -X GET -H "Authorization: Bearer ACCESS_TOKEN_STRING" CALLBACK_URL
  curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer ACCESS_TOKEN_STRING" -d '{"foo" : "bar"}' CALLBACK_URL

יצירת טוקן OAuth לפונקציית Cloud Run

אם אתם מפעילים קריאה חוזרת מפונקציית Cloud Run באמצעות אותו חשבון שירות כמו זרימת העבודה ובאותו פרויקט, אתם יכולים ליצור אסימון גישה מסוג OAuth בפונקציה עצמה. לדוגמה:

const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const token = await auth.getAccessToken();
console.log("Token", token);

try {
  const resp = await fetch(url, {
      method: 'POST',
      headers: {
          'accept': 'application/json',
          'content-type': 'application/json',
          'authorization': `Bearer ${token}`
      },
      body: JSON.stringify({ approved })
  });
  console.log("Response = ", JSON.stringify(resp));

  const result = await resp.json();
  console.log("Outcome = ", JSON.stringify(result));

למידע נוסף, אפשר לעיין במדריך בנושא יצירת תהליך עבודה עם מעורבות אנושית באמצעות קריאות חוזרות.

בקשת גישה אופליין

תוקף טוקנים של גישה פג מדי פעם, והם הופכים לפרטי כניסה לא תקינים לבקשת API קשורה. אפשר לרענן אסימון גישה בלי לבקש מהמשתמש הרשאה, אם ביקשתם גישה אופליין להיקפי ההרשאות שמשויכים לאסימון. בקשה לגישה אופליין היא דרישה לכל אפליקציה שצריכה לגשת ל-Google API כשהמשתמש לא נמצא. מידע נוסף זמין במאמר בנושא רענון של אסימון גישה (גישה במצב אופליין).

הפעלת תהליך עבודה בדיוק פעם אחת באמצעות קריאות חוזרות (callback)

הקריאות החוזרות הן אידמפוטנטיות לחלוטין, כלומר אפשר לנסות שוב קריאה חוזרת אם היא נכשלת, בלי ליצור תוצאות לא צפויות או תופעות לוואי.

אחרי שיוצרים נקודת קצה לקריאה חוזרת (callback), כתובת ה-URL מוכנה לקבל בקשות נכנסות, ובדרך כלל היא מוחזרת למתקשר לפני שהשיחה המתאימה אל await_callback מתבצעת. עם זאת, אם כתובת ה-URL של הקריאה החוזרת עדיין לא התקבלה כשמבצעים את שלב await_callback, הביצוע של תהליך העבודה נחסם עד שנקודת הקצה מתקבלת (או עד שמתרחש פסק זמן). אחרי שהתקבל, הביצוע של תהליך העבודה מתחדש והקריאה החוזרת מעובדת.

אחרי שמבצעים את שלב create_callback_endpoint ויוצרים נקודת קצה של קריאה חוזרת, משבצת אחת של קריאה חוזרת זמינה לתהליך העבודה. כשתתקבל בקשה להחזרת נתונים (callback), המשבצת הזו תתמלא במטען הייעודי (payload) של ה-callback עד שניתן יהיה לעבד את ה-callback. כשמבצעים את השלב await_callback, מתבצע עיבוד של הקריאה החוזרת, והמשבצת מתרוקנת וזמינה לקריאה חוזרת אחרת. אחר כך אפשר להשתמש שוב בנקודת הקצה של הקריאה החוזרת ולהתקשר שוב אל await_callback.

אם הפונקציה await_callback מופעלת רק פעם אחת אבל מתקבלת קריאה חוזרת שנייה, אחד מהתרחישים הבאים מתרחש ומוחזר קוד סטטוס של HTTP מתאים:

  • הקוד HTTP 429: Too Many Requests מציין שהקריאה החוזרת הראשונה התקבלה בהצלחה אבל לא עברה עיבוד, והיא ממתינה לעיבוד. הקריאה החוזרת השנייה נדחית על ידי תהליך העבודה.

  • ‫HTTP 200: Success מציין שהקריאה החוזרת הראשונה התקבלה בהצלחה והוחזרה תגובה. הקריאה החוזרת השנייה מאוחסנת, ויכול להיות שהיא לא תעובד לעולם אלא אם await_callback יופעל בפעם השנייה. אם זרימת העבודה מסתיימת לפני כן, בקשת ה-callback השנייה לא מעובדת ומושלכת.

  • ‫HTTP 404: Page Not Found מציין שתהליך העבודה כבר לא פועל. יכול להיות שהקריאה החוזרת הראשונה עובדה והתהליך הסתיים, או שהתהליך נכשל. כדי לקבוע את זה, צריך לשלוח שאילתה למצב ההפעלה של תהליך העבודה.

התקשרות חזרה מקבילה

כשמבצעים שלבים במקביל, ושרשור אב יוצר קריאה חוזרת שמושהית בשלבי צאצא, התבנית זהה לזו שתוארה קודם.

בדוגמה הבאה, כשמבצעים את השלב create_callback_endpoint, נוצר משבצת אחת של קריאה חוזרת. כל קריאה נוספת ל-await_callback פותחת משבצת חדשה להחזרת שיחה. אפשר לבצע עשר קריאות חוזרות בו-זמנית, אם כל השרשורים פועלים וממתינים לפני שליחת בקשה לקריאה חוזרת. יכול להיות שיתבצעו שיחות חוזרות נוספות, אבל הן יישמרו ולא יעברו עיבוד.

YAML

  - createCallbackInParent:
    call: events.create_callback_endpoint
    args:
      http_callback_method: "POST"
    result: callback_details
  - parallelStep:
    parallel:
        for:
            range: [1, 10]
            value: loopValue
            steps:
              - waitForCallbackInChild:
                  call: events.await_callback
                  args:
                      callback: ${callback_details}

JSON

  [
    {
      "createCallbackInParent": {
        "call": "events.create_callback_endpoint",
        "args": {
          "http_callback_method": "POST"
        },
        "result": "callback_details"
      }
    },
    {
      "parallelStep": {
        "parallel": {
          "for": {
            "range": [
              1,
              10
            ],
            "value": "loopValue",
            "steps": [
              {
                "waitForCallbackInChild": {
                  "call": "events.await_callback",
                  "args": {
                    "callback": "${callback_details}"
                  }
                }
              }
            ]
          }
        }
      }
    }
  ]

שימו לב: שיחות חוזרות מטופלות באותו סדר שבו כל שיחה מתבצעת על ידי סניף אל await_callback. עם זאת, סדר ההרצה של הענפים לא קבוע, ויכול להגיע לתוצאה באמצעות נתיבים שונים. מידע נוסף זמין במאמר בנושא שלבים מקבילים.

איך מנסים תהליך בסיסי של בקשה לחזרה טלפונית

אפשר ליצור תהליך עבודה בסיסי ואז לבדוק את הקריאה לנקודת הקצה של הקריאה החוזרת של תהליך העבודה באמצעות curl. צריכות להיות לכם ההרשאות הדרושות Workflows Editor או Workflows Admin בפרויקט שבו נמצא תהליך העבודה.

  1. יוצרים ומפעילים את תהליך העבודה הבא ואז מריצים אותו.

    YAML

        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "GET"
            result: callback_details
        - print_callback_details:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Listening for callbacks on " + callback_details.url}
        - await_callback:
            call: events.await_callback
            args:
                callback: ${callback_details}
                timeout: 3600
            result: callback_request
        - print_callback_request:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Received " + json.encode_to_string(callback_request.http_request)}
        - return_callback_result:
            return: ${callback_request.http_request}
        

    JSON

        [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "GET"
              },
              "result": "callback_details"
            }
          },
          {
            "print_callback_details": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Listening for callbacks on \" + callback_details.url}"
              }
            }
          },
          {
            "await_callback": {
              "call": "events.await_callback",
              "args": {
                "callback": "${callback_details}",
                "timeout": 3600
              },
              "result": "callback_request"
            }
          },
          {
            "print_callback_request": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\\"Received \\" + json.encode_to_string(callback_request.http_request)}"
              }
            }
          },
          {
            "return_callback_result": {
              "return": "${callback_request.http_request}"
            }
          }
        ]
          

    אחרי שמריצים את תהליך העבודה, הסטטוס של ההרצה הוא ACTIVE עד לקבלת בקשת הקריאה החוזרת או עד שחלף הזמן הקצוב לתפוגה.

  2. מאשרים את מצב ההפעלה ומאחזרים את כתובת ה-URL של הקריאה החוזרת:

    המסוף

    1. נכנסים לדף Workflows במסוף Google Cloud :

      כניסה לדף Workflows
    2. לוחצים על השם של תהליך העבודה שהפעלתם.

      מוצג מצב הביצוע של תהליך העבודה.

    3. לוחצים על הכרטיסייה יומנים.
    4. מחפשים רשומה ביומן שדומה לזו:

      Listening for callbacks on https://workflowexecutions.googleapis.com/v1/projects/...
    5. מעתיקים את כתובת ה-URL להתקשרות חזרה כדי להשתמש בה בפקודה הבאה.

    gcloud

    1. קודם כל, מאחזרים את מזהה ההרצה:
      gcloud logging read "Listening for callbacks" --freshness=DURATION
      מחליפים את DURATION בכמות זמן מתאימה כדי להגביל את רשומות היומן שמוחזרות (אם הפעלתם את תהליך העבודה כמה פעמים).

      לדוגמה, הפקודה --freshness=t10m מחזירה רשומות ביומן שלא נוצרו לפני יותר מ-10 דקות. פרטים נוספים זמינים במאמר בנושא gcloud topic datetimes.

      מוחזר מזהה ההרצה. שימו לב: כתובת ה-URL להתקשרות חזרה מוחזרת גם בשדה textPayload. מעתיקים את שני הערכים כדי להשתמש בהם בשלבים הבאים.

    2. מריצים את הפקודה הבאה:
      gcloud workflows executions describe WORKFLOW_EXECUTION_ID --workflow=WORKFLOW_NAME
      מוחזר המצב של ביצוע תהליך העבודה.
  3. עכשיו אפשר לקרוא לנקודת הקצה של הקריאה החוזרת באמצעות פקודת curl:
    curl -X GET -H "Authorization: Bearer $(gcloud auth print-access-token)" CALLBACK_URL

    הערה: בנקודת קצה של POST, צריך להשתמש בכותרת ייצוג של Content-Type. לדוגמה:

    curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $(gcloud auth print-access-token)" -d '{"foo" : "bar"}' CALLBACK_URL

    מחליפים את CALLBACK_URL בכתובת ה-URL שהעתקתם בשלב הקודם.

  4. במסוף Google Cloud או באמצעות Google Cloud CLI, מוודאים שהסטטוס של הפעלת זרימת העבודה הוא עכשיו SUCCEEDED.
  5. מחפשים את הרשומה ביומן עם הערך textPayload שדומה לזה:
    Received {"body":null,"headers":...

דוגמאות

בדוגמאות האלה אפשר לראות את התחביר.

טיפול בשגיאות שקשורות לפסק זמן

בדוגמה הזו, שמוסיפה על הדוגמה הקודמת, מתבצעת בדיקה של שגיאות שנובעות מזמן קצוב לתפוגה, והשגיאות נכתבות ביומן המערכת.

YAML

    main:
      steps:
        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "GET"
            result: callback_details
        - print_callback_details:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Listening for callbacks on " + callback_details.url}
        - await_callback:
            try:
                call: events.await_callback
                args:
                    callback: ${callback_details}
                    timeout: 3600
                result: callback_request
            except:
                as: e
                steps:
                    - log_error:
                        call: sys.log
                        args:
                            severity: "ERROR"
                            text: ${"Received error " + e.message}
                        next: end
        - print_callback_result:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Received " + json.encode_to_string(callback_request.http_request)}
    

JSON

    {
      "main": {
        "steps": [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "GET"
              },
              "result": "callback_details"
            }
          },
          {
            "print_callback_details": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Listening for callbacks on \" + callback_details.url}"
              }
            }
          },
          {
            "await_callback": {
              "try": {
                "call": "events.await_callback",
                "args": {
                  "callback": "${callback_details}",
                  "timeout": 3600
                },
                "result": "callback_request"
              },
              "except": {
                "as": "e",
                "steps": [
                  {
                    "log_error": {
                      "call": "sys.log",
                      "args": {
                        "severity": "ERROR",
                        "text": "${\"Received error \" + e.message}"
                      },
                      "next": "end"
                    }
                  }
                ]
              }
            }
          },
          {
            "print_callback_result": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Received \" + json.encode_to_string(callback_request.http_request)}"
              }
            }
          }
        ]
      }
    }
      

המתנה לקריאה חוזרת (callback) בלולאת ניסיון חוזר

בדוגמה הזו משנים את הדוגמה הקודמת על ידי הטמעה של שלב ניסיון חוזר. באמצעות פרדיקט מותאם אישית לניסיון חוזר, תהליך העבודה מתעד אזהרה כשמתרחש זמן קצוב לתפוגה, ואז מנסה שוב להמתין בנקודת הקצה של הקריאה החוזרת, עד חמש פעמים. אם מכסת הניסיונות החוזרים מוצתה לפני קבלת הקריאה החוזרת, שגיאת הזמן הקצוב הסופית גורמת לכך שתהליך העבודה ייכשל.

YAML

    main:
      steps:
        - create_callback:
            call: events.create_callback_endpoint
            args:
                http_callback_method: "GET"
            result: callback_details
        - print_callback_details:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Listening for callbacks on " + callback_details.url}
        - await_callback:
            try:
                call: events.await_callback
                args:
                    callback: ${callback_details}
                    timeout: 60.0
                result: callback_request
            retry:
                predicate: ${log_timeout}
                max_retries: 5
                backoff:
                    initial_delay: 1
                    max_delay: 10
                    multiplier: 2
        - print_callback_result:
            call: sys.log
            args:
                severity: "INFO"
                text: ${"Received " + json.encode_to_string(callback_request.http_request)}
    log_timeout:
        params: [e]
        steps:
          - when_to_repeat:
              switch:
                - condition: ${"TimeoutError" in e.tags}
                  steps:
                      - log_error_and_retry:
                          call: sys.log
                          args:
                              severity: "WARNING"
                              text: "Timed out waiting for callback, retrying"
                      - exit_predicate:
                          return: true
          - otherwise:
              return: false
    

JSON

    {
      "main": {
        "steps": [
          {
            "create_callback": {
              "call": "events.create_callback_endpoint",
              "args": {
                "http_callback_method": "GET"
              },
              "result": "callback_details"
            }
          },
          {
            "print_callback_details": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Listening for callbacks on \" + callback_details.url}"
              }
            }
          },
          {
            "await_callback": {
              "try": {
                "call": "events.await_callback",
                "args": {
                  "callback": "${callback_details}",
                  "timeout": 60
                },
                "result": "callback_request"
              },
              "retry": {
                "predicate": "${log_timeout}",
                "max_retries": 5,
                "backoff": {
                  "initial_delay": 1,
                  "max_delay": 10,
                  "multiplier": 2
                }
              }
            }
          },
          {
            "print_callback_result": {
              "call": "sys.log",
              "args": {
                "severity": "INFO",
                "text": "${\"Received \" + json.encode_to_string(callback_request.http_request)}"
              }
            }
          }
        ]
      },
      "log_timeout": {
        "params": [
          "e"
        ],
        "steps": [
          {
            "when_to_repeat": {
              "switch": [
                {
                  "condition": "${\"TimeoutError\" in e.tags}",
                  "steps": [
                    {
                      "log_error_and_retry": {
                        "call": "sys.log",
                        "args": {
                          "severity": "WARNING",
                          "text": "Timed out waiting for callback, retrying"
                        }
                      }
                    },
                    {
                      "exit_predicate": {
                        "return": true
                      }
                    }
                  ]
                }
              ]
            }
          },
          {
            "otherwise": {
              "return": false
            }
          }
        ]
      }
    }
      

המאמרים הבאים