multithreading – c++ multithreaded message broadcaster using callbacks

note: This is an updated design after a previous review round.

I have written a class that handles listeners registering callbacks to receive messages. Registered callbacks can be replaced or removed. Code is multithreaded in that any thread can broadcast to the listeners, and any thread can add, replace or remove listeners.
For this second round, i have made a list of requirements for the class:

  1. One or multiple listeners
  2. listeners can be registered or deregistered from different threads
  3. each listener should be called exactly once for each message
  4. all listeners should receive all messages
  5. messages may originate from multiple sources in different threads
  6. each message should be forwarded to all listeners before the next message is processed (messages are not sent in parallel)
  7. each listener should receive the messages in the same order, i.e., the order in which they are provided to the broadcaster (from the view of the broadcaster)
  8. listeners can be removed or replaced
  9. listeners are uniquely identified by a cookie that remains valid if the listener is not deregistered
  10. the cookie remains valid if the listener is replaced
  11. anyone with the cookie should be able to deregister or replace the listener
  12. listeners should never be called again when deregistration completes
  13. the old listener should never be called again when replace completes
  14. listeners should be able to deregister or replace themselves when invoked, listeners do not need to be able to register more callbacks
  15. listeners are noexcept, so code shouldn’t deal with exceptions thrown by listeners
  16. trying to remove or replace the callback associated with an expired cookie is not an error, the operation should simply do nothing, and report to the caller that nothing was done.
  17. adding, replacing and removing listeners happens much less frequently than broadcasting.

A further consideration that is not enforced in code:

  • Users are expected to read documentation that states which functions are safe to call from inside a listener, and which must not. (Ideal would be a way to check if a function is called from a listener, so we can error in that case. I can’t figure out a way to do that. How to distinguish the case where the lock in, e.g., add_listener can’t be acquired because we’re calling from the callback (can’t call lock, would deadlock), vs where it can’t be acquired because callbacks are running concurrently (calling lock is correct in that case)?)

Implementation

// usage notes:
// 1. This class provides no lifetime management of the link, which means
//    the user is responsible to ensure that either
//    a. the registered callback outlives this broadcaster, or
//    b. they revoke their callbacks before destroying any resource it
//       points to.
// 2. None of the member functions of this class may be called from
//    inside an executing listener, except
//    replace_callback_from_inside_callback() and
//    remove_listener_from_inside_listener(). Calling them anyway will
//    result in deadlock.
// 3. replace_callback_from_inside_callback() and 
//    remove_listener_from_inside_listener() may _only_ be called from
//    inside an executing listener.
namespace detail
{
    enum class Action
    {
        ReplaceListener,
        RemoveListener
    };
}

template <class... Message>
class Broadcaster
{
public:
    using listener   = std::function<void(Message...)>;
    using cookieType = int32_t;

    // should never be called from inside a running listener
    template <class F> requires (std::is_nothrow_invocable_r_v<void, F, Message...>)
    cookieType add_listener(F&& r_)
    {
        auto l = lock();
        _targets.emplace_back(listener{ std::forward<F>(r_) });
        _targetRegistry(++_lastID) = _targets.size() - 1;
        return _lastID;
    }

    // should never be called from inside a running listener,
    // in that case, use replace_listener_from_inside_listener()
    template <class F> requires (std::is_nothrow_invocable_r_v<void, F, Message...>)
    bool replace_listener(cookieType c_, F&& r_)
    {
        auto l = lock();
        return replace_listener_impl(c_, listener{ std::forward<F>(r_) });
    }

    // should never be called from inside a running listener,
    // in that case, use remove_listener_from_inside_listener()
    bool remove_listener(cookieType c_)
    {
        auto l = lock();
        return remove_listener_impl(c_);
    }

    void notify_all(const Message&... msg_) noexcept
    {
        auto l = lock();

        // invoke all listeners with message
        for (const auto& r: _targets)
            r(msg_...);

        // execute any updates to registered listeners
        if (!_mutation_list.empty())
        {
            for (auto&& (cookie, action, r) : _mutation_list)
            {
                switch (action)
                {
                case detail::Action::ReplaceListener:
                    replace_listener_impl(cookie, std::move(*r));
                    break;
                case detail::Action::RemoveListener:
                    remove_listener_impl(cookie);
                    break;
                }
            }
            _mutation_list.clear();
        }
    }

    // !! should only be called from inside a running listener!
    template <class F> requires (std::is_nothrow_invocable_r_v<void, F, Message...>)
    void replace_listener_from_inside_listener(cookieType c_, F&& r_)
    {
        _mutation_list.emplace_back(std::make_tuple(c_, detail::Action::ReplaceListener, listener{ std::forward<F>(r_) }));
    }

    // !! should only be called from inside a running listener!
    void remove_listener_from_inside_listener(cookieType c_)
    {
        _mutation_list.emplace_back(std::make_tuple(c_, detail::Action::RemoveListener, std::nullopt));
    }

private:
    auto lock()
    {
        return std::unique_lock<std::mutex>(_mut);
    }
    bool replace_listener_impl(cookieType c_, listener&& r_)
    {
        auto index = get_target_index(c_);
        if (index)
        {
            // valid slot, update listener stored in it
            auto it = _targets.begin() + *index;
            *it = std::move(r_);
            return true;
        }
        return false;
    }
    bool remove_listener_impl(cookieType c_)
    {
        auto index = get_target_index(c_);
        if (index)
        {
            // valid slot, remove listener stored in it
            auto it = _targets.begin() + *index;
            _targets.erase(it);

            // fixup slot registry, items past deleted one just moved one idx back
            for (auto& (cookie, idx): _targetRegistry)
            {
                if (idx > *index)
                    --idx;
            }
            return true;
        }
        return false;
    }
    std::optional<size_t> get_target_index(cookieType c_)
    {
        auto map_it = _targetRegistry.find(c_);
        if (map_it == _targetRegistry.end())
            // cookie not found in registry
            return {};

        return map_it->second;
    }

private:
    std::mutex                    _mut;

    std::map<cookieType, size_t>  _targetRegistry;
    cookieType                    _lastID = -1;
    std::vector<listener>         _targets;

    std::vector<std::tuple<cookieType, detail::Action, std::optional<listener>>> _mutation_list;
};

Example usage

#include <iostream>
#include <string>
#include <functional>

void freeTestFunction(std::string msg_) noexcept
{
    std::cout << "from freeTestFunction: " << msg_ << std::endl;
}
struct test
{
    using StringBroadcaster = Broadcaster<std::string>;

    void simpleCallback(std::string msg_) noexcept
    {
        std::cout << "from simpleCallback: " << msg_ << std::endl;
    }
    void oneShotCallback(std::string msg_) noexcept
    {
        std::cout << "from oneShotCallback: " << msg_ << std::endl;
        _broadcast.remove_listener_from_inside_listener(_cb_oneShot_cookie);
    }
    void twoStepCallback_step1(std::string msg_) noexcept
    {
        std::cout << "from twoStepCallback_step1: " << msg_ << std::endl;

        // replace callback (so don't request to delete through return argument!)
        _broadcast.replace_listener_from_inside_listener(_cb_twostep_cookie, (&)(auto fr_) noexcept { twoStepCallback_step2(fr_); });
    }
    void twoStepCallback_step2(std::string msg_) noexcept
    {
        std::cout << "from twoStepCallback_step2: " << msg_ << std::endl;
    }

    void runExample()
    {
        auto cb_simple_cookie  = _broadcast.add_listener((&)(auto fr_) noexcept { simpleCallback(fr_); });
        _cb_oneShot_cookie = _broadcast.add_listener((&)(auto fr_) noexcept { oneShotCallback(fr_); });
        _cb_twostep_cookie = _broadcast.add_listener((&)(auto fr_) noexcept { twoStepCallback_step1(fr_); });
        auto free_func_cookie = _broadcast.add_listener(&freeTestFunction);

        _broadcast.notify_all("message 1");  // should be received by simpleCallback, oneShotCallback, twoStepCallback_step1, freeTestFunction
        _broadcast.remove_listener(free_func_cookie);
        _broadcast.notify_all("message 2");  // should be received by simpleCallback and twoStepCallback_step2
        _broadcast.remove_listener(cb_simple_cookie);
        _broadcast.notify_all("message 3");  // should be received by twoStepCallback_step2
        _broadcast.remove_listener(_cb_twostep_cookie);
        _broadcast.notify_all("message 4");  // should be received by none
    }

    StringBroadcaster _broadcast;
    StringBroadcaster::cookieType _cb_oneShot_cookie;
    StringBroadcaster::cookieType _cb_twostep_cookie;
};

int main(int argc, char **argv)
{
    test t;
    t.runExample();

    return 0;
}

api design – Setting custom callbacks in a Javascript applet?

I am assuming based on your description that you are thinking of initializing the application something like this.

<html>
  <body>
    <div id="mount"></div>
    <script type="text/javascript">
      const app = new Visualization(document.getElementById("mount"));
    </script>
  </body>
</html>

or this

<html>
  <body>
    <div id="mount"></div>
    <script type="text/javascript">
      const app = new Visualization().start(document.getElementById("mount"));
    </script>
  </body>
</html>

So I imagine your code looks something like this.

class Visualization {
    constructor(root) {
        this.start(root);
    }

    start(root) {
        // magic
    }
}

So if you want to take callbacks at the start, first you should edit either your constructor or start method to take an object as a parameter.

<html>
  <body>
    <div id="mount"></div>
    <script type="text/javascript">
      const app = new Visualization({
        root: document.getElementById("mount")
      });
    </script>
  </body>
</html>
class Visualization {
    constructor({ root }) {
        this.start(root);
    }

    start(root) {
        // magic
    }
}

This gives you a perfect place to handle optional arguments, since if a key is undefined or null in the object you can just pick a default value.

class Visualization {
    constructor({ root, onLeftClick, onRightClick, onMiddleClick }) {
        const onLeftClickCb = onLeftClick ?? (() => {});
        const onRightClickCb = onRightClick ?? (() => {});
        const onMiddleClickCb = onMiddleClick ?? (() => {});

        // set fields, do whatever
        this.start(root);
    }

    start(root) {
        // magic
    }
}

And that’s basically all there is to it. If you are on older browsers you can use || instead of ??, but either way usage now looks like

<html>
  <body>
    <div id="mount"></div>
    <script type="text/javascript">
      const app = new Visualization({
        root: document.getElementById("mount"),
        onLeftClick: (pos) => { console.log(pos); }
      });
    </script>
  </body>
</html>

And wherever you need to, just assume the function is defined and call it. If none was passed you will just hit the No Op implementation

As a corollary, you shouldn’t need to use getter and setter methods in JavaScript in the same way you would in Java. You can just have public properties and if needed write methods to change their behavior via the get and set keywords.

Time consuming callbacks in customizer

I am having a custom state ex. ‘visibility-state’ which having values as true | false.

I am binding this state to all the settings and settings are in 1000+ numbers. SO when the state changes it is recalling all the states 1000+ times which seems to be a heavy process.

    var setActiveState = function () {
    // This is calling 1000+ times.
                            element.active.set( logic_to_toggle() );
    };

    // This is in the loop of all the controls.
    api.state('visibility-state').bind( setActiveState );

Is there any optimized way to remove all bound functions and set only for the expanded sections so controls under the expanded section would toggle only?

email – Strange callbacks to /unsubscribe url

I have a system that sends bulk emails via Sendgrid. Each email has a personalised unsubscribe link that is handled by a server in our organisation (i.e. not using Sendgrid’s unsubscribe feature.) The link includes a customer ID and a hashed token to validate it. This works fine, but I’m seeing in the logs some callbacks to the /unsubscribe url with a completely different token, e.g. I’m expecting (and getting) callbacks like:

/unsubscribe/1322454:4e549e16fbcf022d0335ab34c8d7764f4885c6e827d4647

But I’m also seeing a handful of callbacks like this:

/unsubscribe/NjUwNzU6Y2

The interesting thing is that all these are originating from IP addresses owned by Microsoft (in the 20.0.0.0 and 40.0.0.0 ranges.) They appear to occur immediately after mail delivery which suggests they are an automated response, though the same IPs also make a matching call to /favicon.ico which suggests they originate from a browser.

It’s not a big deal (the calls just get ignored) but I’m curious as to what is generating these. The tokens all start with N and bear no resemblance to any other data I can find (i.e. not matching message IDs or SMTP delivery responses.)

java – Webscraping tennis data 1.2: Optionals, Streams, Callbacks(?)

Through my previous questions, I developed a web-scraper that gets tennis ranking data.

In this version, I’m back with some modifications for my scraper based on my goals for the overarching project I have in mind. I understand this is a longer post, so I’ve divided my question into thematic sections – hopefully this makes it easier for readers to follow and provide feedback where they can!


What’s changed in the code?

  1. Rather than scrape ALL the weeks, and return a list of WeeklyResults, the scraper now returns a result for a given week. This enables the scraper to scrape a week, pass it on to another function that utilizes the scraped result. Note that it is not truly “asynchronous” yet – more on that later.

    • To facilitate this, Scraper and MyProject have been modified accordingly.
  2. Scraper bug fix #1: certain weeks did not have actual ranking data for the No.1 on the website. Previously, each weekly result was loaded as an Optional in case the player-cell element was empty. However, I had overlooked a case where the first available player-cell was non-empty, but didn’t actually belong to the No.1 player.

    • selectNumberOneRankCell in scrapeWeekly resolves this.
  3. Scraper bug fix #2: Further inspection showed that the empty WeeklyResults would be between stretches of the reign of a given player. With that trend in mind, plus the general likelihood that the current week’s No.1 has a good chance to remain No.1 for the next week (generally), I changed the code to retain the No.1 player from the past week, in the case of an empty scraped result.

    • Added a new field latestResult and modified scrape.
  4. WeeklyResult & ScraperException remain unchanged.


Code:

scraper Package:

WeeklyResult.java

package scraper;

// A POJO that encapsulates a ranking week and the name of the corresponding No.1 player
public class WeeklyResult {
    private final String week;
    private final String playerName;

    public WeeklyResult(final String week, final String playerName) {
        this.week = week;
        this.playerName = playerName;
    }
    public String getWeek() {
        return week;
    }
    public String getPlayerName() {
        return playerName;
    }
}

ScraperException.java

package scraper;

public class ScraperException extends Exception {
    final String message;
    public ScraperException (String message) {
        this.message = message;
    }
    public ScraperException (String message, Throwable cause) {
        super(cause);
        this.message = message;
    }
    @Override
    public String toString() {
        return this.message;
    }
}

Scraper.java

package scraper;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.time.Duration;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

public class Scraper {
    private static final Logger logger = LogManager.getLogger(Scraper.class);
    private final String urlPrefix;
    private final String urlSuffix;
    private final Duration timeout;
    private final int totalTries;
    private WeeklyResult latestResult;

    public Scraper(final String urlPrefix, final String urlSuffix, final Duration timeout, final int totalTries) {
        this.urlPrefix = urlPrefix;
        this.urlSuffix = urlSuffix;
        this.timeout = timeout;
        this.totalTries = totalTries;
        this.latestResult = new WeeklyResult("1973-08-16","N/A");
    }

    public WeeklyResult scrape(final String week) throws ScraperException {
        // in the case the latest scraped data returns an "empty" weekly result, simply retain the latest No.1
        // since it is likely he wouldn't have changed. A weekly result is deemed empty if no player or week info
        // can be found on the ATP page.
        this.latestResult = scrapeWeekly(week)
                .orElse(new WeeklyResult(updateLatestWeekByOne(), this.latestResult.getPlayerName()));
        return this.latestResult;
    }

    private Optional<WeeklyResult> scrapeWeekly(final String week) throws ScraperException {
        final Document document = loadDocument(weeklyResultUrl(week));
        final boolean numberOneDataExists = selectNumberOneRankCell(document).isPresent();
        final Element playerCell = numberOneDataExists ? selectPlayerCellElement(document) : null;

        return Optional.ofNullable(playerCell)
                .map(element -> new WeeklyResult(week, element.text()));
    }

    public List<String> loadWeeks() throws ScraperException {
        final Document document = loadDocument(urlPrefix);
        final Elements elements = selectRankingWeeksElements(document);
        final List<String> weeks = extractWeeks(elements);

        return noEmptyElseThrow(weeks);
    }

    private Document loadDocument(final String url) throws ScraperException {
        Document document = null;
        for (int tries = 0; tries < this.totalTries; tries++) {
            try {
                document = Jsoup.connect(url).timeout((int) timeout.toMillis()).get();
                break;
            } catch (IOException e) {
                if (tries == this.totalTries) {
                    throw new ScraperException("Error loading ATP website: ", e);
                }
            }
        }
        return document;
    }

    private static Elements selectRankingWeeksElements(final Document document) {
        // extract ranking weeks from the dropdown menu
        final Elements result = document.getElementsByAttributeValue("data-value", "rankDate")
                .select("ul li");

        Collections.reverse(result);
        return result;
    }

    private static List<String> extractWeeks(final Collection<Element> elements) {
        // refer to https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/
        // and https://www.baeldung.com/java-maps-streams.
        return elements.stream()
                .map(Scraper::extractWeek)
                .filter(week -> Optional.ofNullable(week).isPresent())
                .collect(Collectors.toList());
    }

    private static List<String> noEmptyElseThrow(final List<String> weeks) throws ScraperException {
        if (weeks.isEmpty()) {
            throw new ScraperException("Cannot process empty data from the weeks calendar!");
        } else {
            return weeks;
        }
    }

    private String weeklyResultUrl(final String week) {
        return urlPrefix + "rankDate=" + week + urlSuffix;
    }

    private static Optional<Element> selectNumberOneRankCell(final Document document) {
        final Element rankCell = selectPlayerRankCell(document);
        return Optional.ofNullable(rankCell).filter(element -> numberOneRankCellExists(element));
    }

    private static Element selectPlayerCellElement(final Document document) {
        return document.getElementsByClass("player-cell").first();
    }

    private static boolean numberOneRankCellExists(final Element rankCell) {
        return rankCell.text().equals("1");
    }

    private static Element selectPlayerRankCell(final Document document) {
        return document.getElementsByClass("rank-cell").first();
    }

    private static String extractWeek(final Element li) {
        return li.text().replaceAll("\.", "-");
    }

    private String updateLatestWeekByOne() {
        return LocalDate.parse(this.latestResult.getWeek()).plusWeeks(1).toString();
    }
}

myproject Package:

MyProject.java

package myproject;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
import scraper.Scraper;
import scraper.ScraperException;
import scraper.WeeklyResult;

import java.time.Duration;
import java.util.List;

// Main class to manage the visualization of player's legacy rankings
public class MyProject {
    private static final Logger logger = LogManager.getRootLogger();

    private static void utilizeScrapedResult(WeeklyResult weeklyResult) {
        // pass the scraped result to the next stage of the visualization logic.
        logger.info("Week: " + weeklyResult.getWeek() + " No.1: " + weeklyResult.getPlayerName());
    }

    public static void main(String() args) {

        Configurator.setRootLevel(Level.DEBUG);

        final Scraper scraper =
                new Scraper("https://www.atptour.com/en/rankings/singles?",
                        "&rankRange=0-100", Duration.ofSeconds(90), 3);

        // The flow is as follows: scrape the latest weekly results (starting from 1973),
        // then pass it to the ranking logic (IPR). Rinse and repeat
        try {
            final List<String> weeks = scraper.loadWeeks();
            for (String week : weeks) {
                WeeklyResult weeklyResult =  scraper.scrape(week);
                utilizeScrapedResult(weeklyResult);
            }
        } catch (ScraperException e) {
            System.out.println(e.toString());
        }
    }
}

Scraper Code: Optionals, Streams and Style Checks

  1. I want to make sure I’m not abusing the concept of an Optional. I believe I’m not, since both the player-cell and rank-cell generally hold values relevant for us, but occasionally don’t. One thing that was a bit sticky, though, was the fact that I didn’t really have a neat way to relate rank-cell elements to player-cells. Logically, I wanted to say: “The rank-cell element is empty if the first available one on the given page is not that of the actual No.1’s. Select the player-cell element if the rankCell is actually present.” This is the best I could come up with:

    
    final boolean numberOneDataExists = selectNumberOneRankCell(document).isPresent();
    final Element playerCell = numberOneDataExists ? selectPlayerCellElement(document) : null;
    

    It’d be neat to know if there is a better way of achieving this.

  2. Have I used Streams properly, specifically in the selectNumberOneRankCell & extractWeeks functions?

  3. Any other style concerns would be appreciated. I think the addition of latestResult should be good, please let me know if I’m overlooking something!


MyProject Code – Optimizing the Scraper design, Asynchronicity & Callbacks.

NOTE: Since this involves looking at my design, which could be off-topic, I will keep it short. If it is off-topic, please let me know and I’ll remove it and repost to a more appropriate site.

In general, the code in MyProject involves chaining separate pieces of logic. Eg. scrape a WeeklyResult, pass it on to utilizeScrapedResult, which does its work and constructs something, say a WeeklyRanking, that is passed to the next logical section and so on. Would my current code structure be efficient to handle this as the number of separate pieces of logic increases, or should I switch to using callbacks as suggested?

  • In this context, a given piece of logic would only be dependent on its output in the preceding timestamp. Eg. the WeeklyRanking for week B would have to be preceded by the WeeklyRanking for week A, but the WeeklyResult for week B could be scraped (and stored somewhere) before the WeeklyRanking of week A is computed. On the flip side, a WeeklyResult for week A cannot be constructed after the WeeklyResult of week B. (I forget the mathematical term used to describe this relation…)

Feedback on any other aspects of the code that need to be addressed are welcome. If you made it this far, thank you for your time!

7 – Menu hook callbacks not working for anonymous users

My custom error handler module is somewhat based on the customerror module, but instead of returning markup, my menu callback function redirects based on the original path. It registers error paths as follows:

function my_customerror_menu() {
  $items = array();

  $items('my_customerror/%') = array(
    'title callback' => FALSE,
    'access callback' => TRUE,
    'page callback' => 'my_customerror_page',
    'page arguments' => array(1),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

'access callback' => TRUE should make it accessible to all users. I can use the path my_customerror/404 for example in the site settings and it is recognized as a valid path. This works fine in a local instance and on the Pantheon development site, but once I pushed it to the Pantheon test environment, my_customerror/404 is not invoked on 404 – Drupal uses the default error response. Unless I am logged in, then it works in Test as well.

But the path my_customerror/404 works regardless when I type it in manually and does what it should. Somehow Drupal erroneously thinks that this path is not accessible to anonymous users.

What could be causing this?

Here is a simplified version of the callback function that shows what it does:

function my_customerror_page($code) {
  $dest = variable_get('my_customerror_' . $code . '_default', '');
  switch ($code) {
    (conditional stuff)
    default:
      header('Status: 301 Moved Permanently', false, 301);
      header("Location: " . $dest);
      break;
  }
  return "";
}

Don’t ask why I use header() and not drupal_goto() ….

Just bringing something into the problem description that I already mentioned in a comment. When the Devel module is turned on and anonymous users are given permission to see developer output, it starts working! But this is not a solution, I obviously can’t do this in the Live environment.

unity – Should I generate Untiy callbacks documentation?

I’m using Doxygen in my Unity project. I’ve set the EXTRACT_PRIVATE to YES so that I can document private methods and I’m currently using the #if ndef and #endif# commands around Unity’s callbacks to be able to avoid these on the document generation.

My only question is whether there should be any documentation on these methods. On the one hand everybody know what they do and they have their own documentation. On the other hand, in each script they will call different function and do different tasks. This however will make the code redundant as the documentation will show what the code is intended to.

What should I do?

8 – Ajax Multiple Callbacks does not work for the first callback?

I tried to load the data into another picklist field from the first picklist reminder.

I want to modify the two variables in the selection list according to the first modification selection list, please refer to the code example below to understand.

function example_form_alter(&$form, FormStateInterface $form_state) {
     $form('field_example')('widget')('#ajax') = (
      'callback' => 'example_ajax_callback',
      'wrapper' => 'example-company-wrapper',
      'event' => 'change',
      'progress' => (
        'type' => 'throbber',
        'message' => t(''),
      ),
    );

function example_ajax_callback(&$form, FormStateInterface $form_state) {
  $first_callback = example_company_form_trigger(&$form, FormStateInterface $form_state);
  $second_callback = example_company2_form_trigger($first_callback, $form_state);
  return $second_callback;
}

function example_company_form_trigger(&$form, FormStateInterface $form_state) {
return $form('field_example1');
}

function example_company2_form_trigger(&$form, FormStateInterface $form_state) {
return $form('field_company2');
}

When the ajax callback calls, the second callback (example_company2_form_trigger) only works, the first callback (example_company_form_trigger) does not work.

I am faced with this problem when returning the form.

Could someone help me solve this problem?

c # – How to add a value to a context with callbacks following the Fluent Builder template

I use Fluent Builder with reminders to configure a context on the fly.

Currently, I have a PlaneContext class with a list of passengers that I want to populate using the method AddPassenger as:

var builder = new PlaneBuilder();

builder.AddPlane(plane => plane.Name = "Boeng") 
    .AddPassenger(passenger => passenger.Name = "Robert")
    .AddPassenger(passenger => passenger.Name = "Johnny");

As stated above, I created a PlaneBuilder chain class methods like these. The first method AddPlane works well, but the chained methods AddPassenger does not work as expected. My lack of understanding on the part of the delegates Action and Func is the question.

That's what I tried, but I can not convert implicitly string at Passenger:

public PlaneBuilder AddPassenger(Func callback)
{
    var passenger = callback(new Passenger());
    _planeContext.Passengers.Add(passenger);
    return this;
}

Currently, here is how PlaneBuilder the class looks like:

class PlaneBuilder
{

    private PlaneContext _planeContext = new PlaneContext();

    public PlaneBuilder AddPlane(Action callback)
    {
        callback(_planeContext);
        return this;
    }

    public PlaneBuilder AddPassenger(Func callback)
    {
        // Goal is to create a passenger
        // Add it to the passengers list in PlaneContext
        var passenger = callback(new Passenger());
        _planeContext.Passengers.Add(passenger);
        return this;
    }
}

Finally, here is the PlaneContext classroom:

class PlaneContext
{
    public string Name { get; set; }
    public string Model { get; set; }
    public List Passengers { get; set; }
}

In short, how can I add passenger to PlaneContext using callbacks?

javascript – works in nodejs with callbacks

I followed a tutorial and found an example with promises that looked like this:

function requestName(userName){
	
	const url = `https://api.github.com/users/${userName}`;
	fetch(url)
		.then( function(res){
			return res.json(); 
		})
		.then( function(json){
			console.log(json.name);
		})
		.catch( function(e){
			console.log(`El error es: ${e}`);
		});
		
}

I still learn about the functions but I was trying to do the same thing with Callback but the result does not print anything on the console or maybe I'm doing something wrong, the code is the next:

function requestName(userName){
	const url = `https://api.github.com/users/${userName}`;
	fetch(url, function(err,res){
		if(err){
			console.log(`El error es: ${err}`);
		}else{
			const json = res.json();
			console.log(json);
		}
	})
}