Post

Is GSoC just Promise.any()

Is GSoC just Promise.any()

GSoC season is approaching

You have your READMEs all updated

Let’s make sure you are ready to be an “Open Source Contributor”

Let’s understand what your OSS journey and JS Promises have in common.

Promise

You are an avid browser of GitHub with contribution graph drier than Sahara.

You friend Harsh who started as “Black Hat Hacker” in 2018 to “Crypto Trader” in 2020 is your best Tech Bro

Harsh advices you a remedy for that GitHub. He says, “Yahi tarika hai bro FANG mein jane ka, senior bhaiya bhi aise hi gye the”

The advice is to contribute to a popular repo @github

You realize the repo is by a 10x developer, you hesitate.

Harsh appears, excalims, “Be confident bro! Bas README mein typo fix kar. Start small. Maintainers notice consistency.”

So you fork the repo, change images in README and open a PR. And you wait!

The state you are in is exactly the same as a Promise.

In JavaScript, a Promise represents:

A value that will be available in the future.

In OSS terms:

Your PR has three possible states:

StateDescription
PendingMaintainer hasn’t reviewed
FulfilledPR merged
RejectedPR closed

The default state is pending. Once it’s merged or closed there’s no undo, i.e, State is permanent

Promise Creation

1
2
3
4
5
6
7
8
const firstPR = new Promise((resolve, reject) => {
  const merged = Math.random() > 0.5;
  if (merged) {
    resolve("LGTM 🎉");
  } else {
    reject("Get a life");
  }
});

(resolve, reject) => { ... }

this part is known as the “executor function”

The moment line 1 executes, two things happen:

  1. Executor function runs immediately.
  2. Promise is born in the Pending state.

The PR is open yet no response from maintainer but the process has begun

State Decision

Inside the executor, only two functions matter:

  • resolve(value)
  • reject(reason)

Whichever gets called first decides the fate of the Promise.

Just like whichever button the maintainer clicks first decides your PR.

1
2
resolve("LGTM 🎉"); // PR merged
reject("Get a life"); // PR closed

The moment one of the function runs, the state of your Promise changes:

FunctionState
ResolveFulfilled
RejectRejected

If none of the functions are called, your Promise stays in the pending state just like your PR.

Then

Let’s say the maintainer sees an opportunity to save more famous repos from your “Contributions” and merges your PR.

You would be elated afterall you have become an “Open Source Contributor”

What you do after this change of state is handled by .then()

1
2
3
firstPR.then((message) => {
  console.log("Tweeting screenshot:", message);
});

Upon calling .then() following happens:

  • It attaches a fulfillment handler
  • It runs when the Promise becomes Fulfilled
  • It returns a new Promise

Since it returns a new Promise, we can be certain the old Promise firstPR is not touched but rather a new Promise is created based on firstPR outcome

Whatever the previous .then() returns becomes the value for next Promise. This is called Promise Chaining

1
2
3
4
5
firstPR
  .then(() => "Time to write real feature")
  .then((nextStep) => {
    console.log(nextStep);
  });

Promise inside Then

What if I return another Promise?

You’re learning! Let’s say you do something like this

1
2
3
4
5
6
7
firstPR
  .then(() => {
    return new Promise((resolve) => {
      setTimeout(() => resolve("Feature merged"), 1000);
    });
  })
  .then(console.log);

The second .then() waits until the returned Promise resolves.

It ensures control in the chain.
Just like you don’t apply for GSoC until your contributions are real.

Catch

We can handle a PR but what about a rejection?

Let’s say your beautiful README changes were deemed unworthy by that arrogant maintainer. We need to tweet about that too!

This is where we use .catch()

1
2
3
4
5
6
7
firstPR
  .then(() => {
    throw new Error("Build failed");
  })
  .catch((err) => {
    console.log("Fixing:", err.message);
  });

.catch() only runs when Promise becomes rejected. Funnily enough, .catch() also returns a new Promise so the previous drill applies

Throw inside Then

What if I throw inside .then()?

The whole chain becomes rejected

The error propagates down the chain.

It skips all subsequent .then() calls until it finds a .catch().

Example:

1
2
3
4
5
6
7
8
9
10
firstPR
  .then(() => {
    throw new Error("Build failed");
  })
  .then(() => {
    console.log("This will NOT run");
  })
  .catch((err) => {
    console.log("Caught:", err.message);
  });

Output:

1
Caught: Build failed

Why Does This Happen?

Because under the hood .then()

is conceptually something like this:

1
.then(successHandler, undefined)

If you throw(), JS automatically converts that into:

1
return Promise.reject(error);

So throwing inside .then() is just a cleaner way of rejecting the next Promise in the chain.

Finally

That was about tweets, we can be a bit casual on twitter but that won’t do on LinkedIn.

HR needs to see that you take rejections on the chin and move forward with “New Learning, a New Chapter”

You need to frame your README PR as if that was the peak of your coding career.

We will use .finally() when you want something to run regardless of the outcome

1
2
3
4
5
6
7
8
9
10
firstPR
  .then(() => {
    console.log("I Love Open Source");
  })
  .catch(() => {
    console.log("Maintainers are arrogant");
  })
  .finally(() => {
    console.log("My latest contribution taught me everything about life. Here are my insights!");
  });

.finally() returns nothing, it’s just final cleanup crew to make sure everything was done.

What if I throw inside .finally()?

You have become too powerful for your own good.

Throwing inside .finally() overrides everything. The whole chain becomes rejected

Static Methods

Till now you were a solo contributor working alone.

If we recall from git blogs, we can understand that OSS contribution is not about a single contributor.

Let’s work in a team (or at least pretend to). Now the contributors consist of you, Harsh and Akash. We will call this squad “ChaiWale”

All

ChaiWale decide, “aaenge to sab sath mein varna koi nhi”

Let’s say you do something like this:

1
2
3
4
5
6
7
Promise.all([yourPR, harshPR, akashPR])
  .then((results) => {
    console.log("All merged:", results);
  })
  .catch((err) => {
    console.log("One failed:", err);
  });

Which results in:

  • Resolve only if ALL promises fulfill
  • Reject immediately even if ONE rejects

If all promises resolve, you get an array of results:

1
["LGTM 🎉", "LGTM 🎉", "LGTM 🎉"]

The order is preserved even if Akash’s PR merged first,

AllSettled

ChaiWale has matured, you say, “Jo hua so hua. Pehle dekhte hain kya hua”

1
2
3
4
Promise.allSettled([yourPR, harshPR, akashPR])
  .then((results) => {
    console.log(results);
  });

AllSettled() does:

  • Waits for ALL promises to settle
  • Never rejects
  • Returns structured results

Example output:

1
2
3
4
5
[
  { status: "fulfilled", value: "LGTM 🎉" },
  { status: "fulfilled", value: "We don't like tech bros" },
  { status: "rejected", reason: "TM 🎉" }
]

Race

ChaiWale are getting competetive, you say, “Jo pehle merge karega, uska LinkedIn post sab share karenge”

1
2
3
4
5
6
7
Promise.race([yourPR, harshPR, akashPR])
  .then((winner) => {
    console.log("Hero of the day:", winner);
  })
  .catch((err) => {
    console.log("First response was rejection:", err);
  });

Race() does:

  • Settles as soon as the FIRST promise settles.
  • If first result is rejection, whole thing gets rejected.
  • Doesn’t wait for others.

Any

ChaiWale has not realized inner fighting doesn’t result in any merged PRs. Afterall, one friend need to join somewhere to give others a referral.

You say, “Yaar koi ek to aage badho, sare nalle hi pade rhoge kya”

1
2
3
4
5
6
7
Promise.any([yourPR, harshPR, akashPR])
  .then((winner) => {
    console.log("Squad survives because of:", winner);
  })
  .catch((error) => {
    console.log("Sab reject. Time to grind.");
  });

Race() does:

  • Resolves when the FIRST promise fulfills
  • Ignores rejections
  • Rejects only if ALL reject
  • Throws AggregateError if everyone fails

Conclusion

Throughout your journey, you learned alot about Promises and Open Source.

ChaiWale is now a mature group of Devs who don’t chase after merged PRs, rather meaningful contributions to projects they use and admire.

Maybe one day, ChaiWale will create something of their own and won’t be waiting for resolve()

:wq

This post is licensed under CC BY 4.0 by the author.