Nicholas C. Zakas's Blog

December 14, 2022

Setting up a Rust development environment with Visual Studio Code

When I decided to teach myself Rust, I naturally started looking around at what editors I could use. It turned out that most of the folks I asked online were still using Visual Studio Code for Rust. To my surprise, though, setting up a complete development environment in Visual Studio Code wasn’t as straightforward as I expected. I need to find and download different tools in order to get started.

(I’m assuming you already have Visual Studio Code installed, but if not, go install it.)

Step 1: Download and install the rustup

The easiest starting point for most folks is to install rustup. If you’re coming from Node.js development, the closest analog would be the nvm in that rustup not only installs everying that you need to start Rust development but also allows you to easily switch back and forth between release channels and install additional components.

When you install rustup, you get a complete Rust toolchain including the compiler (rustc) and the cargo (closest Node.js analog is npm). With those installed, you can start compiling Rust programs immediately.

You can read more about rustup in the rustup Handbook.

Step 2: Create a project

Technically it’s not necessary to create a project to proceed to the next steps, but it will help you ensure that Visual Studio Code is set up correctly. So, create a simple project to get started by running:

cargo new hello-world

This command creates a new directory called hello-world that contains a Rust project scaffold, including:

Cargo.toml - the equivalent of package.json for Rust src/main.rs - the Rust source file to execute

You can then cd into hello-world and run:

cargo run

This will execute the src/main.rs file and should print out “Hello world” if everything is installed correctly. (You can think of cargo run as an analog to npm start.)

Now we are ready to prepare Visual Studio Code for Rust development!

Step 3: Install the rust-analyzer Visual Studio Code extension

The most important Visual Studio Code extension to install is . You can think of rust-analyzer as the equivalent of the default Visual Studio Code JavaScript language server (which include TypeScript) and ESLint. Without rust-analyzer, you are basically just using Visual Studio Code as a text editor without all of the additional type checking, lints, and code completion that you expected.

Once installed, you can open up src/main.rs (or any other Rust file) and you’ll get the Visual Studio Code experience that you expect. (If you get an error message saying that rust-analyzer couldn’t find a Rust workspace, that just means the Cargo.toml file is missing or invalid.)

Important: Make sure you cargo run your project at least once before opening a .rs file in Visual Studio code. The rust-analyzer extension requires the build information found in the target directory in order to do things like code completion and symbol lookup. If it seems like you aren’t getting any of that info in Visual Studio Code, stop and run cargo run. If that still doesn’t work, make sure the file you are working on is referenced in main.rs or lib.rs. Files that aren’t referenced by your primary file in some way aren’t compiled and so rust-analyzer doesn’t know about them.

Step 4: Install the CodeLLDB Visual Studio Code extension

Out of the box, you won’t be able to debug Rust code in Visual Studio Code. You’ll need to install either the extension or the extension. Because the Rust compiler is build on LLVM, you’ll need one of these two extensions to generate the debug information you’ll need to debug Rust programs in Visual Studio Code. Most folks seem to prefer CodeLLDB, like because it’s not Microsoft-related, but either will work.

Bonus Step: Install the crates Visual Studio Code extension

While this extension isn’t strictly necessary, the extension helps ensure that you are using the most up-to-date versions of your dependencies. It does this by placing a small green checkmark to the right of each dependency in your Cargo.toml file.

The crates extension showing up-to-date green checkmarks in Cargo.toml

Conclusion

It took me several days to get Visual Studio Code set up for Rust development by finding all of the various tools and extensions that needed to be installed to work properly. Once set up, though, Visual Studio Code is just as suitable a development environment as any other editor or IDE could be for Rust. The rust-analyzer extension, in particular, gives you almost everything you need to develop Rust as rapidly as JavaScript in Visual Studio Code. Adding in the other extensions really pushes Visual Studio Code into the “great” category for Rust development.

 •  0 comments  •  flag
Share on Twitter
Published on December 14, 2022 16:00

June 13, 2022

Sponsoring dependencies: The next step in open source sustainability

When the JavaScript Standard Style (StandardJS) project1 decided to show ads during installation, the backlash was swift and harsh. The project is an opinionated JavaScript style guide, formatter, and linter all in one, and it was also the first npm project to try and raise money by inserting an ad in the command line.2 The “experiment” was terminated soon after it was started.3 But to me, this opened up a big question: given that StandardJS is simply a wrapper around ESLint that disables customization, what responsibility does StandardJS have to ESLint?

Open source maintainers should be able to accept sponsorships for their work, but does that funded project have a responsibility to the project on which it was built to pass some portion of the funds along? After all, if the majority of the benefit of your project is based on the work of someone else’s project, is it really fair for you to profit?

Isn’t there some level of responsibility when a project receives funding to help all of the projects on which it is built?

The stratified open source ecosystem

When people say “open source,” it could mean any number of things. Vue,4 the UI framework upon which many modern web applications are built, is an open source project with 2.8 million weekly npm downloads; he,5 a small npm package that helps with encoding and decoding HTML entities in strings, is also an open source project, and it has 13.5 million weekly npm downloads. Which one would you rather donate to? Does one inherently deserve more funding than the other?

Some may say that both projects have a responsibility to find their own funding. They are both equally capable of setting up a website to accept donations. They are both equally capable of reaching out to individuals and companies for funding. They are both equally capable of giving presentations about their project to attract interest. But there is a fundamental difference between these two projects.

The reality is that we live in a stratified open source ecosystem. The very top spot is reserved for those projects that get a lot of name recognition and use. Projects like Vue that are end-user facing (the result is visible directly to end-users because the user interface is built with it) and those that are developer-facing (developers specifically choose to use the project) get a lot of attention as people share their experiences, best practices, tooling, testing, and other tips and tricks to best use the project. Because Vue is constantly being mentioned, the project gets more mindshare, and that makes it easier to attract sponsorships; because Vue is end-user facing, it’s easier to understand its value proposition to the companies that use it. Both companies and developers are more motivated to support these projects both financially and through code contributions.

On the other end of the spectrum, there are ecosystem projects that provide utilities or low-level functionality, like he, that most people will never hear of. These projects are included as dependencies by the projects that companies and developers choose to use, and so sit in the background without much visibility. And even if companies and developers hear about a dependency like he, would they think it’s important enough to fund? Can you imagine approaching a company for funding and explaining that your utility encodes and decodes HTML entities? They would probably think you were joking, and in any case would laugh.

There is no doubt that Vue requires more ongoing maintenance and development than he, so he clearly doesn’t need as much funding, but does it deserve zero? Yet, Vue depends on he, so clearly it has value to Vue. What responsibility does Vue then have to he?

The consequences of stratified open source

We have already seen major security issues in open source projects that weren’t properly funded. The heartbleed bug in OpenSSL essentially made the web unsafe until it could be fixed.6 At the time (2014), OpenSSL was receiving just $2,000 per year in donations and had one developer working on it. OpenSSL is still a foundational piece of internet technology, embedded in everything from web browsers to web server software, and yet it was terminally underfunded. Eventually, the Linux Foundation stepped up to pledge support to ensure that such an important project was never left without funding again.

Closer to the JavaScript ecosystem, the creator of faker.js and colors.js Marak Squires intentionally crippled7 the projects over frustration about a lack of funding. He is quoted as saying he was, “no longer going to support Fortune 500s (and other smaller-sized companies) with my free work.” He had tried to set up a GitHub Sponsors account and an Open Collective account, but he still wasn’t able to make enough money.

If a foundational piece of internet security like OpenSSL has struggled with a lack of funding for years, how do you think that the conversation would have gone asking to sponsor faker.js (a project for creating fake data) and colors.js (a project to add colors and styles into console applications) would have gone?

The harsh reality is that not all open source projects have the same opportunity to raise funds. So how can we expect these projects to survive?

With great power…

It’s too easy to say that each project should be responsible for its own funding when there is such a clear stratification of projects. If a company has an open source sponsorship budget, a package like he has very little chance of competing for those funds against Vue. Vue will win 99% of the time. When scaled across all open source projects, we end up with a small number of large projects that receive the majority of funding, and then a large number of smaller projects that receive little or no funding. And when the bigger projects rely on the smaller projects, this is unsustainable. A single broken low-level dependency can risk the entire larger project and all of its users.

The solution to this problem really is simple: open source projects should fund their own dependencies. I would even go so far as to say this isn’t just the best solution to the problem, it is, in fact, a responsibility of those larger, well-funded projects to support their dependencies. The spirit of open source is one of giving recognition to those pieces upon which your software is built, and there isn’t any reason that recognition can’t be extended to funding.

There are several projects in the JavaScript ecosystem that receive over $100,000 each year in donations, including:

Babel8 - $303,000 Webpack9 - $250,000 ESLint10 - $190,000 Vue11 - $150,000 (plus unknown amount on GitHub Sponsors)

Imagine if each of these projects carved out a percentage of their sponsorships to give to their dependencies. At 10%, that would mean over $80,000 would end up in the hands of other projects that are less able to attract funding. That amount of money would make a significant difference to smaller projects.

A practical sponsorship pledge

What does a practical dependency sponsorship program look like? Certainly, a project can’t be expected to give away the majority of its funding to dependencies, and I wouldn’t suggest that. Instead, there are a few simple steps maintainers can take to get started:

Budget to give away 10% of your funding to your dependencies. The best way to plan is just to budget right from the start. In most cases, 10% of your funding isn’t enough to hurt your project’s maintenance and development. If in your case it is too much, then cut it down to 5%. Or 1%. The important part is that you plan to start supporting your dependencies on a monthly basis by budgeting for it. Start with the have-nots and work your way up. When deciding which projects to sponsor, start with the ones that have little or no funding. You can use BackYourStack12 to identify your dependencies and determine if they have an Open Collective account. If they don’t have a mechanism set up to accept donations, you can always pledge them on Open Collective13 or reach out and encourage them to set up a way to donate. It’s fine to start with sponsoring one project rather than trying to spread funding across multiple. As you raise more money, you can always sponsor more projects. It doesn’t make sense to donate money to projects that already have more money than yours. Even though ESLint uses Webpack, Webpack has more funding than ESLint, so it doesn’t make sense for ESLint to donate to Webpack. Instead, ESLint donates to dependencies like lint-staged, which have significantly less funding. Reward excellence. In addition to financial need, you may want to add other criteria that show how well the project is maintained. Does it have a proper open source license? Does it have a code of conduct? You can use donations as a way to help level up your dependencies in addition to supporting them.

In this way, projects of any size can contribute to the well-being of their dependencies. Sponsorships then flow from the largest, best-funded projects all the way down the dependency tree to the zero-dependency packages. Even if your project is receiving as little as $100 each month, you can still make a difference to another project that isn’t receiving any funding.

A call to action: do your part

If you are the maintainer of a well-funded open source project, now is the time to step up. For too long, we have sat back and taken in sponsorships for our own use even as the projects that we depend upon have struggled to find funding. We have the power and responsibility to improve the situation for maintainers of all open source projects. ESLint14 and Astro15 have already established programs to donate to dependencies. You can join them in spreading the wealth around.

If you maintain a smaller open source project that needs funding, figure out if there are any well-funded projects that depend on your project directly and contact the maintainer. Feel free to ask for their financial support and reference this blog post. Make sure that you have set up both a GitHub Sponsors16 account and an Open Collective17 account to make it easy to accept donations. Don’t be afraid to approach these folks and ask for their help. They have benefited from your work for a long time, so you’re not asking for something in return for nothing; you’re asking for compensation for the value you’ve already provided.

If you are sponsoring an open source project, ask them what their dependency support program looks like. Sponsors are in a unique position to apply pressure on projects to distribute their funds appropriately. For the ESLint project, we have heard from sponsors that we were chosen over other projects partly because we were supporting our dependencies. As in any marketplace, the people with the money have an outsize effect on how the marketplace runs.

We all need to do our parts to ensure that we are moving towards an open source ecosystem where the most valuable projects don’t struggle with funding. Let’s open up a downstream transfer of funding wherever possible.

Conclusion

“We need to fund projects based on what we use rather than what we see.”– Ben Nickolls, Executive Director, Open Source Collective


Open source isn’t going to survive if we keep doing things the same way they’ve been done. Relying on individual donations of $5-$10 each month isn’t anywhere near enough for most projects to survive. The only solution is corporate funding. And while more companies are willing to donate to open source, the number of projects they are willing to donate to is small and often limited to what is providing the most immediate value to them. Those projects that are fortunate enough to have the visibility and mindshare to attract funding have a responsibility to their dependencies to ensure the entire ecosystem’s survival. If we can end up in a place where companies donate to a small number of open source projects, and those projects, in turn, donate to others, we will help spread funding throughout the entire ecosystem and truly create a path to sustainability.

Thanks to Fred Schott and Ben Nickolls for reviewing early drafts of this post.

References

JavaScript Standard Style 

npm install funding 

StandardJS Pauses Experiment with Ads in the Terminal after Linode Pulls Sponsorship 

Vue 

he 

Tech giants, chastened by Heartbleed, finally agree to fund OpenSSL 

JavaScript dev deliberately screws up own popular npm packages to make a point of some sort 

Open Collective: Webpack 

Open Collective: Babel 

Open Collective: ESLint 

Open Collective: Vue 

BackYourStack 

Open Collective: Make a Pledge 

ESLint: Supporting our dependencies 

Astro dependencies donation announcement 

GitHub Sponsors 

Open Source Collective: What We Offer 

 •  0 comments  •  flag
Share on Twitter
Published on June 13, 2022 17:00

December 27, 2021

Making your open source project sponsor-ready, Part 3: Accepting sponsorships

In the previous two posts in this series, I described why companies sponsor open source projects1 and how following some basic project hygiene can help attract sponsors2. Now that your project is functioning at a high level and is attractive to companies, it’s time to talk specifics about accepting sponsorships from companies.

Making it easy for companies to donate

If you only accept donations through Bitcoin, paper checks, or direct deposit into your personal checking account, what signal do you think you’re sending to companies? How you accept sponsorships largely determines how many you will receive in two ways:

Establishes (or destroys) trust with the company Makes it easy (or hard) for the company to donate

Companies are not going to create a new process to pay you. You need to find a way to accept donations using the processes that companies already use. By accepting donations through a trusted intermediary, you’re signaling that you’ve taken the time to think through your sponsorship program and are comfortable with some transparency and traceability when it comes to your sponsorships. Both are important for making companies comfortable with donating. Fortunately, there are two excellent options available:

Open Collective3 - Open Collective is an open source-focused sponsorship site that is popular among many projects and contributors. Open Collective offers a number of different ways for companies to donate, including through direct debit, lump sum payments that are distributed to many projects over time, and gift cards. Many large companies signed up for Open Collective early on and are familiar with the platform. Donations are received through the Open Source Collective, which is a nonprofit, which may be important for some companies. There is a small fee for handling funds. GitHub Sponsors4 - If a company cares about open source, then they likely have a corporate GitHub account. That is good news for you because they can donate through GitHub Sponsors using their existing billing relationship with GitHub. If you already have a GitHub account, there’s no reason not to set up a GitHub Sponsors page. It’s free, and GitHub doesn’t take any fees from the donations collected. You can also have money from GitHub Sponsors transferred to your Open Collective account so the money is all in one place (more on why you might want to do this later).

Once you have set up your funding options, be sure to configure your funding.yml file so GitHub will display a sponsor button on your project page.5 You can add Open Collective, GitHub Sponsors, and other links that will appear on your project page.

Action item: Set up Open Collective, GitHub Sponsors, or both. Set up your GitHub project to show how people can donate.

Establish where sponsor logos will go and how they get there

As discussed in part 2, companies will sponsor your project to generate good publicity for them in the open source community. The most common way to deliver this to your sponsors is through placing their logos in highly visible places. If you’ve ever been to a tech conference then you’ve probably seen sponsor logos placed on the projector screen in between or at the end of talks as well as on the print schedules. As a maintainer, you are trying to deliver the same level of attention to your sponsors.

Projects typically display their sponsors’ logos in at least two places:

Website - if your project has a website, then displaying sponsor logos on the homepage is the best way to promote your sponsors. Some projects also choose to show these logos throughout the site, sometimes in a sidebar or a footer. README - at a minimum, sponsor logos should be displayed on your README. Sometimes the README gets more views than a project’s website. Not every user will go to your website but most will take a look at the README. Because READMEs are often copied to other locations (such as npm for JavaScript projects), sponsor logos get the most visibility.

Once you’ve determined where the sponsor logos will go, the next step is to set up some automation to update those logos. Both Open Collective and GitHub offer APIs that allow you to pull your sponsor information for this purpose. Investing in this automation early will save you the time it takes to manually track down, resize, and place logos in the correct places for each new sponsor.

Action item: Determine where your sponsor logos will go and establish automation to update logos in those locations.

Explain how the funds are (or will be) used

If you’ve ever driven along a highway and seen a sign next to some construction that reads “Your tax dollars at work,” you’ll know the importance of letting sponsors know how their money is being spent. Similarly, another consideration for accepting sponsorships is how you will spend the funds. It’s important to think this through before you accept your first sponsorship because companies will want to know your plans for the money.

This is where having a roadmap (discussed in part 2) helps. It’s an easy story to tell that money collected will go towards fulfilling the plans on the roadmap. While it’s not easy to estimate how much it costs to implement a feature, it can help to think about how many hours it will take. Then, multiply that by some hourly rate you’d feel comfortable working at and use that as the cost to implement that feature. That way, you can put together a funding goal that companies can contribute towards. For instance, let’s say that you estimated the cost to implement your 12-month roadmap is $20,000 USD. You can then set up your goal on Open Collective and GitHub Sponsors and explain how much of your funding goal needs to be reached to implement which parts of your roadmap.

The next step after a target funding goal is to describe how the money will actually be spent. Not every maintainer wants to work 40 hours a week on their project, nor is that required to receive sponsorships. ESLint, for example, started receiving sponsorships in 2019 and has never (up to the time of my writing) had a single full-time maintainer. Other projects like Babel and Webpack do have full-time maintainers. How you plan to spend your sponsorship money is up to you, but it is helpful to explain that to potential sponsors.

The best place to do this is right alongside your call for donations, but you can also do it in a blog post or a paragraph on your README. Just make sure this information is readily available for any potential sponsors who come along. Here are some examples:

Donations will be used to pay the maintainer(s) with an eventual goal of working full time on the project. Donations will be used to pay the maintainer(s) with an eventual goal of working one eight-hour day each week on the project. Donations will be used to pay all contributors for their contributions. Donations will be used to fund community events, T-shirts, promotional materials, and speaking engagements. Donations will be used to implement a specific feature or hire outside help.

After you receive your first sponsorships, be sure to update everyone on how the money was actually spent. This can also be in the form of a blog post or a message to your sponsors (both Open Collective and GitHub Sponsors allow you to send messages directly to your sponsors). In general, it’s beneficial to post this information publicly because it can also send a signal to potential new sponsors about how your project is operating with the money it has already collected.

If you are using Open Collective to disburse funds, all transactions are visible to the public, which is a great way to show how the funds are being used. This is why you may want to consider having your GitHub Sponsors funds deposited into your Open Collective account. That way, all funds can be disbursed from Open Collective in a public ledger that makes it easy for sponsors to see how the funds are being used and also allows you to easily generate reports off the transaction data.

Regardless of how you decide to explain how funds are used, make sure you issue regular updates. Never give the appearance that the money is disappearing into a black hole. It’s going somewhere, so be sure to tell that story.

Action item: Come up with a plan for how sponsorship money will be used and publish it on your README or website in addition to Open Collective and GitHub Sponsors.

Be prepared to say “no”

An underrated part of accepting sponsorships is to choose your sponsors wisely. While you may think any company that is willing to give you money is a sponsor you’d want, think again. Some companies will sponsor your project because they use it internally and want to ensure its success, but others will see sponsorship a lot like buying an advertisement: it’s just publicity in exchange for money, and they don’t care what happens to the project.

Open Collective, in particular, has a problem with less-than-reputable companies signing up to sponsor projects just for publicity. While Open Collective is putting more controls in place to limit these companies’ ability to donate to projects, you’ll still need to monitor incoming sponsorships to ensure that these are companies you want to do business with.

Aside from ensuring your sponsors align with your values, you also need to make sure you’re accepting sponsorships from companies that other companies want to have their logo next to. If your first couple of sponsor logos are for overseas online casinos, is that something Google or Microsoft or Amazon would want their logos next to? Of course not.

Your first sponsorships, in particular, may have a disproportionate impact on how potential sponsors see your project. If your first logo is Microsoft, that will encourage other companies to sponsor your project; if your first logo is Joe’s Tackle and Sports Book Shop, that’s not going to entice new sponsors.

Action item: Create a sponsorship policy that lists out what types of companies you will (or won’t) accept sponsorships from. Post this publicly so everyone is aware, though keep in mind that few companies will read this upfront. It’s just helpful to be able to point them to your policy when questions arise.

Conclusion

Early on in ESLint’s fundraising, several companies told us they would sponsor the project if we signed up for Open Collective. Not all companies will be that forward with projects they want to sponsor. That’s where the suggestions in this post come in. By using trusted third parties to collect donations, display sponsor logos, explaining how you’ll use the funds, and being careful about which companies you accept sponsorships from, your project will be more attractive to companies. Meeting companies where they are, especially if they are already sponsoring other open source projects, is the best way to get started.

I hope you’ve enjoyed this three-part series about making your open source project sponsor-ready. I wish your project a lot of success and sponsors.

Thanks to Fred Schott and Ben Nickholls for reviewing early drafts of this post.

References

Making your open source project sponsor-ready, Part 1: Companies and trust 

Making your open source project sponsor-ready, Part 2: Project hygiene 

Open Collective 

GitHub Sponsors 

GitHub - Displaying a sponsor button in your repository 

 •  0 comments  •  flag
Share on Twitter
Published on December 27, 2021 16:00

December 20, 2021

Making your open source project sponsor-ready, Part 2: Project hygiene

In part 11 of this series, I described how companies make decisions about spending their money and why they might (or might not) sponsor an open source project. If you haven’t yet read that post, I’d suggest going back to do so now before continuing. Everything in this post builds off the topics discussed in part 1, the most important of which is establishing a level of trustworthiness as both a maintainer and a project.

You are either building or destroying trust with everything that you and your project do online. Making sure that the trail you are leaving speaks well of both is important for attracting potential sponsorships. Fortunately, most of the steps are straightforward and, if you’ve been running your project for some time, you are likely already doing them.

Use an OSI-approved license

The Open Source Initiative (OSI), which is the organization that officially defined the term “open source,” has a list of approved open source licenses.2 These licenses have been fully verified to be open source licenses, ensuring software using these licenses can be “freely used, modified, and shared.” This list is used by governments, companies, and lawyers when evaluating open source projects.

Double-check to make sure you are using an OSI-approved license without any custom modifications. Using other licenses creates a legal gray area for companies, which means they need to spend time reviewing the licenses with their legal team, and that might prevent them from using the project or sponsoring it. Companies are more likely to sponsor free and open source software than they are projects with custom licenses that might send a signal that the project isn’t going to be free and open source forever. Don’t make the companies do work to validate your license.

If you have already started your project with one license, don’t be afraid to change it. There are numerous projects that have changed their licenses based on feedback from their users, including JSHint3 (which inherited the infamous “no evil” license from JSLint) and React4. Depending on how dramatic the license change, you may want to adjust your version number accordingly.

If you are just starting out and unsure what license to use, I personally prefer the Apache 2.0 license5. This license contains an explicit patent grant that companies tend to favor as opposed to the MIT or BSD license, neither of which mention patents in any way (which makes some companies nervous).

Action item: Double-check that your project is using an OSI-approved license and feature the license prominently on the README.

Set up a code of conduct

Imagine that you are in charge of open source sponsorships at a company and someone suggests a certain project. Wanting to do your due diligence, you go to the project’s GitHub page and look through recent issues and pull requests. What you see is a maintainer who is swearing and being defensive, commenters insulting code submissions, and just overall aggressive behavior by more than a few people. What message does that send about the project? Is that a recipe for attracting contributors and long-term support? Will a company want to be associated with such behavior? Of course not.

How you behave as a maintainer, and to a larger extent, the behavior of those who are collaborating on your project, reflects on the project as a whole. For the best example, look no further than Linux, which for years had a culture of conflict that ultimately led to the formal adoption of a code of conduct6. Don’t wait for things to reach that point. Establishing a code of conduct early on in the life of a project makes it more appealing to both contributors and sponsors.

The Contributor Covenant[^7] is a good place to start if you don’t want to do much research. Keep in mind that you are expected to follow your code of conduct as well. Make sure your behavior is above reproach while interacting with (sometimes irrational) people on GitHub. Companies absolutely do look at issues to see how things are being handled. (And you never know when a new contributor might turn into an ally in getting their company to sponsor your project.)

Action item: Establish a code of conduct and feature it on your README and in GitHub.

Publish documentation

A project without users is a project without sponsors, and one of the biggest barriers to open source project adoption is a lack of documentation. At a minimum, your project needs two sets of documentation:

Getting Started Guide - this documentation is targeted at your project’s users and guides them through installation and setup. If there are any prerequisites to installing your project, this should also be mentioned. The guide should continue through to using the project (executing it if it’s an executable, using the API if it’s an API, etc.). Developer Guide - this documentation is targeted at people who want to contribute code. At a minimum, this should describe how to get the source code and set up a local development environment. Ideally, it also describes any commit message formatting you require, the process for accepting contributions, and anything else between writing code and submitting a pull request.

Where the documentation lives is up to you. To start, it’s fine to put most of the documentation on your README. As the documentation grows, you may want to have a dedicated documentation folder in your project that the README links to. Eventually, if your project gets popular enough, having a dedicated documentation website will help your users tremendously.

Remember, the more users and contributors you have, the more likely one of them will lead to a sponsorship, and documentation is a key part of attracting people to your project.

Action item: Write your Getting Started and Developer guides. Include them in, or link to them from, your README.

Develop a roadmap and release plan

The most important question people ask about a project is “What’s next?” Both users and sponsors want to know where the project is headed. For users, they want to know that the project is continuing to grow and develop; for sponsors, they want to know what their money is paying for. To answer both groups, it helps to develop a roadmap and a release plan.

A roadmap is simply a list of the work you plan to do within a specific time period. Your roadmap can be for any length of time you think is reasonable, but typically they are in the range of three months to one year. On your roadmap, list out anything that you think is important for people to know is coming. Features are an obvious addition to any roadmap, but you can also add bug fixes, documentation updates, integration tasks, and more. People just want to know how you plan on spending your time to improve the project going forward, and a roadmap lays this out for easy reference.

A release plan is a companion to a roadmap and establishes a schedule for when to expect releases. A lot of smaller open source projects have a “whenever I feel like it” release plan that, unfortunately, doesn’t work as the project grows. Larger projects, and especially those that would like sponsorship, are well-served to come up with a release plan so users know when changes are coming. For example, Node.js has a well-defined release plan[^8] based on six-month intervals; ESLint does minor releases every two weeks. You can choose a schedule that works best for you. The most important thing is that this schedule is communicated so you can answer the question “When will this be released?”

When you do a release, be sure to explain how it relates to your roadmap. The easiest way to do this is to generate release notes from your commit history. GitHub can generate these release notes for you[^9] based on your pull request labels. Release notes are an important part of the release process so users and sponsors can see what changes are made.

Action item: Come up with a roadmap for the next year and establish a release plan for your project. Document both, either on your README or with your other documentation.

Be responsive on GitHub

No one starts an open source project dreaming of the fun they’ll have triaging issues and pull requests, yet maintaining a project often means spending a significant amount of time doing so.

Your level of responsiveness on GitHub speaks volumes about your level of commitment to the project. Few things say “abandoned project” more than GitHub issues and pull requests that don’t get responses from maintainers.

Does that mean you need to rush to respond to every issue or pull request? No. Those will come in 24 hours a day, 7 days a week, and it’s not reasonable to expect maintainers to be on call for every question or suggestion. However, you should have a regular schedule for responding to issues and pull requests. Maybe you respond just once or twice a week, and that’s fine. It’s more important to come up with a schedule you can keep than it is to respond immediately. If you know you won’t be able to respond within a week, then you might want to note this on your README or with an automated message on your issues and pull requests.

Action item: Come up with a schedule for dealing with incoming issues and pull requests. Optionally, publish this schedule in your README or with an automated message.

Establish support channels

Even though you’ll spend time writing documentation, people will still have questions about your project, and for that, you’ll need to establish one or more support channels. You probably don’t want people opening issues for every question they have, so where would you like those questions to go? You can choose whatever will work best for you. Some popular options include:

GitHub Discussions Discord or Slack A mailing list A forum

It’s reasonable to just have one support channel for your project based on your preferred way of communicating, or you can establish multiple support channels if you have the team to respond. The important thing is that users (and sponsors) have a way to contact you that doesn’t involve opening an issue.

Action item: Set up at least one support channel for your users and mention it on your README.

Establish communication channels

Along similar lines, it’s important for you to have a way to communicate with your users directly. When there is a new release, or a new team member joins, or a new sponsor signs up, you need a way to let your project’s community know about these changes. Once again, the channel(s) you choose should fit with how you prefer to communicate. Some popular options are:

Blog Twitter account Announcements channel in Slack/Discord Mailing list

Whichever channels you choose to use, be sure that they are listed in your README (and website, if you have one). It’s important for users to know where they can go to get the latest information about the project.

Action item: Establish one or more communication channels and add them to your README and website.

Conclusion

In this post, I’ve described some basic open source hygiene that all projects, regardless of sponsorship status, should follow. Doing all of these things won’t automatically result in sponsorships, but they will set your project up for success when seeking sponsors. High-quality, valuable projects are the ones that companies are more likely to sponsor, and following the suggestions in this post helps set your project apart from a hobby project that a maintainer might lose interest in after a few weeks. The more professional your project appears, the easier it will be to find sponsors.

Now that your project is practicing good hygiene, it’s time to prepare for the actual sponsorships. I’ll cover that topic in my next post.

Thanks to Fred Schott and Ben Nickholls for reviewing early drafts of this post.

References

)[^7]: Contributor Covenant[^8]: Node.js - Releases[^9]: GitHub - Automatically generated release notes

Making your open source project sponsor-ready, Part 1: Companies and trust 

Open Source Initiative Approved Licenses 

JSHint - 2020 Relicensing 

Relicensing React, Jest, Flow, and Immutable.js 

Apache License Version 2.0 

[Linus Torvalds apologizes for years of being a jerk, takes time off to learn empathy](https://arstechnica.com/gadgets/2018/... 

 •  0 comments  •  flag
Share on Twitter
Published on December 20, 2021 16:00

December 13, 2021

Making your open source project sponsor-ready, Part 1: Companies and trust

Early on, it was a battle to get sponsorship for open source projects. What used to require phone calls and drawn-out discussions has now been streamlined thanks to efforts like Open Collective1 and GitHub Sponsors2. Companies and individuals can now know if a project accepts donations just by looking at the project page on GitHub, and if you’re lucky, they’ll sign up without you needing to do anything. But is that really all you need to do? Just set up an Open Collective or GitHub Sponsors page and just watch the money roll in? Not quite. There’s a lot you can do to make your project attractive to potential sponsors.

While it’s possible to bring in a decent amount of money through individual sponsorships, the real path to open source sustainability is to get larger donations from the companies that depend on your project. Getting $5 to $10 each month from a bunch of individuals is nice, but not as nice as getting $1,000 each month from a bunch of companies. For that reason, this post focuses on making your project attractive to companies, and to do that, you first need to understand why a company might want to sponsor your project.

Why companies sponsor open source projects (or don’t)

There’s a fairly common mantra online about why companies should sponsor open source projects: because it’s the right thing to do. Many companies, especially startups, are able to get started because they are building on top of free and open source software. It would be difficult or impossible for companies to compete without the availability of zero-cost foundational software. Once they are profitable, it makes sense for companies to contribute back to the software that helped them become successful. Right?

The harsh reality is that companies don’t operate as charities. Their goal is to bring in as much money as possible, and that doesn’t include giving away money “because it’s the right thing to do.” For some, this is frustrating, but if you can move past the perceived unfairness of it all, you can come up with a strategy that works. Just because companies won’t sponsor your project out of gratitude for your work doesn’t mean they won’t sponsor your project. You just need to stop and think about the things that companies do spend money on regularly and then align your offering with those things.

And there are two things that companies readily spend money on: helping the business and publicity.

Companies invest in things that help the business

As discussed in my previous post3, companies generally spend money on things that accomplish one of three goals:

Saves time Saves money Generates more money

Open source projects must fulfill at least one of these goals to make it attractive to sponsor. Your project might save the company time by providing code that they would otherwise have to build and maintain themselves; your project might save them money because otherwise they’d need to buy a commercial product; your project might generate them money if it is user-facing. So the first thing to understand is what value your project is providing to companies.

In the business world, this is referred to as your value proposition. It’s often a one-liner that tells everyone exactly why your product is valuable. As an example, if you visit ESLint’s website, you’ll see this phrase featured prominently: “Find and fix problems in your JavaScript code.” That’s it. Simple, easy-to-understand. If you write JavaScript code, then ESLint is helpful for you. It helps by catching problems you might miss, and problems cost your company time and money; therefore, ESLint saves your company time and money.

Action items: Define the value proposition for your project and feature it prominently: on your README, on your website, on your social media accounts, etc. Make it a simple one-line sentence that can help explain how it saves companies time or money, or helps to generate more money.

Companies pay for good publicity

Companies are willing to pay for achieving their goals, but there is another consideration: will the sponsorship reflect well on the company? All companies care about their image in the marketplace and will not spend their money on anything that reflects badly on them. Think of the commercials you see while watching TV. Companies are buying advertising spots during specific shows because they want their brand associated with the show. Why? To generate more money by reaching the fans of that show. The opposite is also true: companies pull their ads when it becomes a risk to their reputation.

Make sure to have a website in addition to your README, as open source projects frequently promote their sponsors through logo placement on both. Companies need to be okay with having their logo in these spots, which means they need to be okay with the association between their company and your project. As a simple example, if you name your open source project “hotGirlXxXparse,” it’s doubtful that a company will want their brand associated with your project.

The ideal case is that the company gets good publicity for supporting your project. Companies care about publicity like this because it helps to attract new hires and retain their existing developers (similar to sponsoring tech conferences). There is a lot of enthusiasm for open source projects in the tech community, and publicly thanking a company for sponsoring your project helps to build their brand among the community. While companies may not expect much of an impact on their brand from sponsoring a project, it can result in more name recognition and even an increase in candidates for open jobs.

Overall, your job is to make your project into something that companies would be proud to have their brand associated with.

Action items: Ensure you’ve named your project appropriately and have a professional-looking website with spaces for company logos. Make sure the website is some place a company would be excited to be displayed.

The importance of building trust with companies

So you have defined your value proposition, picked a good name, and have a nice website up and running. The next step is just as important: how to signal to companies that you are trustworthy. A sponsorship is a business agreement, and you are a partner to that agreement. In order to do business with you, someone at the company must trust you. Companies do not do business with people or other companies they don’t trust.

Startups are intimately familiar with this problem. How do you get customers when you have no track record and no other customers? How do they know you’ll still be around in a year? How do they know you won’t just take their money and run? With open source projects the problem is even more complicated because there often isn’t a company backing it up (if there is, they might not need sponsorship, after all).

Your job from this point forward is to do everything you can to appear as trustworthy as you can. Remember, you are trying to convince a for-profit business to give you, some person who wrote some software on the weekend, a significant amount of money. This is no small task. You need to think of yourself more like a business than an individual and spend time building your reputation.

In my next post, I’ll discuss some concrete ways to build the trust of both you and your project.

Thanks to Fred Schott and Ben Nickholls for reviewing early drafts of this post.

References

Open Collective 

GitHub Sponsors 

How to talk to your company about sponsoring an open source project 

 •  0 comments  •  flag
Share on Twitter
Published on December 13, 2021 16:00

May 17, 2021

How to talk to your company about sponsoring an open source project

Open source sustainability is a topic that is just starting to get the attention that it deserves. So much of the technology sector is run on software that can be used for free without any further obligation. However, as companies profit from using this software for free, the maintainers of the software often struggle to find enough time to work on the software while making enough money to survive. Many are forced to work full-time jobs to subsidize the development of these open source projects, or else try to work full-time on the project while making less than they would in the industry. While donations from individuals help, the only real way to make open source sustainable for maintainers is for the companies who reap the outsize benefits of the software to donate towards their maintenance.

This post contains everything I know about convincing a company to support open source projects. I’ve worked with many companies to donate to ESLint, the open source project that I’ve founded, and in doing so, I’ve learned a lot about how these efforts are organized. My hope is that this post will serve as a guide to help others convince their companies to donate to the open source projects that support their businesses.

Getting your story straight

Before you start talking to people about sponsoring an open source project, it’s important to take some time to craft the story you’ll tell them. A lot of developers, with their hearts in the right place, tell a story that donating to an open source project is “the right thing to do.” While that might actually be true, most companies don’t operate on the basis of altruism. Companies don’t exist to give away their hard-earned money; they exist to make more money. If you want to convince your company to donate money to an open source project, you’re going to have to make the case that doing so actually benefits the company (outside of warm fuzzy feelings).

Generally speaking, a company will spend money on something that accomplishes any of these three goals:

Saves time Saves money Generates more money

Your job is to make the case that the open source project helps achieve one or more of these goals. In most cases, you can argue that the project saves you time because it prevents you from needing to implement the functionality yourself. In some cases, you can argue that the project bootstraps common tasks that would otherwise take you weeks or months to accomplish. If the project is a code quality tool like ESLint then you can argue that it saves you time by catching bugs or other quality issues before they happen. Really take the think to think through how the project is benefitting your workflow.

Once you’ve tied the open source project to at least one of the three goals, imagine what would happen if the project was no longer maintained. How long does it takes before that absence starts to work against the three goals? How much will it slow you down or cost you money if you couldn’t use the project anymore? That is why donating to the project is valuable: your workflow is dependent on this project and removing the project now would cause harm, either through time or money. Therefore, it makes sense to donate to the project in order to maintain the advantage you’ve already realized for the foreseeable future.

Crafting the presentation

After putting together a good story of why it makes sense to donate to the project, the next step is to present that story to the correct person in your organization. It makes sense to start with your manager to get their support, though you may need to present the same information multiple times in order to get approval. The format of this presentation will be dependent upon the norms of the company, so you should be prepared to put together an email, a PowerPoint presentation, or an explainer, depending on your company’s preferences.

Regardless of the format, your presentation should answer the following questions for your audience:

What does the project do? What 2-3 sentences describe the purpose of the project? Assume your audience doesn’t know anything about it and introduce both the problem the project solves and what the solution looks like. Where is the project used in your company? Is it used in a continuous integration environment? In your text editor? Is it customer facing (like a UI framework)? This gives context to your audience about how integrated the project is in your current workflow. It’s likely your audience may not even be aware the project is in use. What value do you get from using the project? Does it lead to fewer bugs? Faster feature development? Does it generate more revenue? Again, assume your audience knows nothing about the project and how you’re using it. Remember to include at least one of the three goals mentioned earlier in this post. Who receives the money? When you donate the money, where does it end up? In a foundation? In an individual’s bank account? You’ll have better luck getting a donation to a project if the project is set up on a reputable site like Open Collective1 or GitHub Sponsors2. It’s difficult to convince your company to send a large amount of money to a PayPal email address. If the project isn’t already on Open Collective or GitHub Sponsors, it’s worth reaching out to the maintainers to ask them about this. How does the project use the money it receives? Are they paying maintainers? Do they use money for infrastructure? Again, you can’t argue that it’s “nice” to pay maintainers of a project. Be explicit about how the money is used. In many cases, it will go directly to pay the maintainers, so that’s important to call out. It can help to mention if the maintainer doesn’t otherwise have a salary, as it lends to the importance of paying the maintainers to ensure the project will continue to be maintained. What does the company get in return for the donation? A logo on the README or website? Is there a priority support channel? Make sure there is some direct benefit for the sponsorship. Companies will be looking for some sort of recognition of their donation (explained later in this post). If the project doesn’t offer anything in return, reach out to the maintainers and ask at least for a sponsorship notice in the README or website. Many maintainers will be happy to do that and it gives you a better argument. What commercial alternatives are available and what do they charge? Would a paid alternative cost more or less than donating to support the open source project? This doesn’t apply to all projects, but if there is a commercial alternative, this can be a powerful part of your story because you can tie actual dollar amounts to the value of the project. If a commercial solution costs $10,000 and you’re asking the company to donate $1,000 to the open source version, that comes off looking like a big cost savings to the company. What other companies are sponsoring the project? Are any big companies sponsoring the project? If you want to donate to a project that already has some corporate sponsors, it can help to call those out. A project that is sponsored by a well-known company such as Google or Facebook creates social proof that it is not just okay but expected to sponsor this project. As an added bonus, potentially getting your company’s logo next to Google or Facebook’s logo could be a selling point. If there aren’t any other sponsors, it’s best not to bring that up unless asked directly.

Pulling all of this information together can take some time, but ultimately, these are the details you will need to convince your company to sponsor an open source project.

Hopefully, you will get approval for sponsoring a project not long after making the presentation. Your work is far from over, though. To move forward, you need to understand a little bit about how companies manage their money, and in doing so, where you can find the money to sponsor a project.

Finding the money

Companies generally work on budgets at two levels: yearly and quarterly. The yearly budget for the next year is set towards the end of the fourth quarter of the current year and is typically when the top-level budget items (sometimes called cost centers) are established. You can think of these as defining the row labels in a spreadsheet – these are the categories under which funds are assigned. The quarterly budget for the next quarter is established towards the end of the current quarter, and typically you cannot add budget items during the quarterly process, you can only adjust the amounts assigned to each item. This is important to understand because it is unlikely that you’ll be able to get funding for an open source project within three months. You’ll need to get the request into one of the windows for the yearly or quarterly budget planning process, and sometimes that can take several months.

Because getting a budget item added is difficult and make take a year, it’s usually easier to work within the existing budget items to find funds that can be used for open source. Here are some budget items that are typically used to sponsor open source projects:

Dedicated Open Source Fund IT/Software Marketing Recruiting

Each of these budget items have different reasons why they might be appropriate, so it’s helpful to dig in to the details.

Dedicated Open Source Fund

Obviously, if your company has a dedicated open source fund, then that is where the money should come from. This is money that has already been set aside from the company to sponsor open source projects and there is likely more money in there than is being used at the moment. It may be as simple as filling out a form or emailing the right person to get a new project considered for sponsorship. This is a good sign that your company is a leader when it comes to open source sustainability and the process for devoting funds to a project is already established.

Your ultimate goal should be to get a dedicated open source fund budget item established at your company because that is the only way to ensure that (or at least increase the likelihood that) the funds will actually be used for open source sponsorships. Whenever you are dipping into other budget items to sponsor open source, it’s possible for those funds to be reallocated to something else each quarter.

IT/Software

Any modern company runs on a large amount of software, whether that be Gmail, GitHub, AWS, Salesforce, or any number of other software-as-a-service products. As such, there is usually a dedicated budget for this type of software, and once again, there is usually some extra dedicated for unanticipated software needs during the quarter. Because open source software is, afterall, software that your company is relying on, you can make a strong argument that funds in this budget item can be used to sponsor a project.

If your company doesn’t already have a dedicated open source fund, looking at the IT/Software budget item is a good place to start. Your manager or director should have direct insight into whether this is possible because they likely receive a specific amount each quarter. The amount you receive is likely to go on in the future unless you stop using the project you want to sponsor.

Marketing

One of the underrated aspects of sponsoring open source projects is just how much good publicity it brings to companies. Companies spend a lot of money on something called brand awareness, which is basically whether or not people know the company in the industry. Google, Facebook, and Apple all have great brand awareness: you know what they do and how they benefit you. The result is more sales, and also, an easier time recruiting new employees. So how does sponsoring an open source project increase brand awareness?

Companies regularly spend thousands of dollars sponsoring tech conferences to get their logo in front of 1,000 people for three days. Many open source projects offer logo placement on their READMEs and websites, which can easily reach more eyes even for projects that aren’t well-known. Plus, open source projects are good at promoting their sponsors. ESLint, for example, tweets out a public thank you and will also publish a blog post touting the sponsorship. This type of publicity is a big win for companies, as it is a grassroots effort that shows the company believes in supporting its developers and tools they use.

Getting access to these funds often takes a bit of digging. As always, you can start with your manager, but you’ll ultimately need to find someone who works in marketing to talk with and explain the opportunity. Just be careful: the marketing department thinks in terms of “campaigns,” which are fixed-length periods of spending a certain amount of money to get a certain result. In short, sometimes this ends up being equated to an ad buy and you’ll need to discuss renewing the commitment after some period of time.

Recruiting

As previously mentioned, brand awareness is important to the company for a number of reasons, and one of those is the ability to attract and hire new employees. Even though a good amount of these efforts fall under marketing, some are recruiting-specific. Recruiting departments regularly spend money on sourcers (those folks who email you directly asking if you’d like to apply), software like LinkedIn and Indeed, booths at college job fairs and tech conferences, and swag like t-shirts, cups, and hats. All of this is to say: recruiting spends a lot of money to try and attract job candidates.

Sponsoring an open source project is a good way to attract job candidates. As long as the project does a decent job at promoting its sponsors (and most of them do), your company is likely to get in front of exactly the people who they are looking to hire. Developers know what open source projects they prefer to use, and if they can find a company where they can no only use it but also contribute to it, that’s a big selling point for a job applicant.

I put this as the last option of the four because it tends to be the hardest to get and is also prone to strict timelines after which you’ll have to discuss renewing the contribution. Your manager likely has someone who they work with in recruiting regularly, and so that person is a good place to start the conversation. Going through recruiting will likely take longer than going through other budget items, but it is an option nonetheless.

Sponsorship approaches

At this point, you have hopefully identified where in the budget the money for open source sponsorships will come from. The next step is to determine what type of sponsorship your company will make. As with finding money in the budget, there are many approaches that companies take when sponsoring open source projects. You’ll need to find the solution that best matches your company’s culture and approach to open source. Fortunately, there are many options to choose from.

Monthly contributions

Open source projects generally prefer monthly contributions to keep the project funded. If at all possible, getting your company to commit to donating a monthly amount for 12 months gives the project the most financial stability. There are ongoing costs to maintaining an open source project and having a reliable amount of money coming in every month helps the maintainers to plan ahead of time. Platforms like Open Collective and GitHub Sponsors allow you to pledge contributions both monthly and yearly. When specifying a yearly donation, the amount can be set up to be evenly split among the upcoming 12 months.

This option works well if your open source donations are coming from your IT/Software budget item, as most software is billed on either a monthly or yearly basis.

Open source grants

For some companies, the idea of donating monthly will not be the best option. It feels like a commitment without an end, and that can be a hard sell. Another approach is to create an open source grant, which is a one-time donation to a project based on some set of criteria. It’s up to the individual companies to determine what those criteria are, and there is no one right way to do it.

Companies like Indeed3, Microsoft4, and Salesforce5 have set up Free and Open Source Software (FOSS) Contributor Funds, where the employees who have contributed to open source projects during the quarter are then allowed to nominate and vote for open source projects to receive a one-time donation (typically $10,000). FOSS Contributor Funds tend to be popular within their host companies because they allow the developers themselves to determine which projects are considered for a donation.

Some companies also provide open source grants that must be applied for. Mozilla6, the Sloan Foundation7, Google8 9, and Comcast10 have grant programs where anyone is welcome to apply for a one-time grant. The decisions are typically made by a committee who is dedicated to reviewing and choosing grantees.

Grants are a great way to support open source projects if your company isn’t willing or able to provide ongoing monthly support. Grants are less favored by open source projects because there is far less predictability of income, but in the end, any donations are appreciated. There is also a bit of a downside for the company because there is administrative overhead associated with a grant program.

Using grants is more beneficial if you end up using marketing or recruiting budget to fund open source projects because there is a set amount and duration.

Employee gift cards

Another option that is available exclusively through Open Collective is to give open source gift cards11 to employees. The gift cards can then be used to fund any project that is on Open Collective. With this approach, the company would transfer some amount of money to Open Collective in a lump sum. Gift cards would then be issued for any number of employees, with each gift card representing a set amount of that lump sum. The employees are then free to use those gift cards to donate directly to any project they want, so long as it is capable of receiving funds through Open Collective.

This can be a nice option for companies who can’t commit to a monthly or yearly donation and who don’t want the overhead of running a grant program.

Other things to keep in mind

There are just a few other notes I’d like to share as your work on convincing your company to sponsor an open source project:

Don’t expect that you can hand off this initiative to someone else. Your manager is unlikely to take this on as their own task. In every company I’ve worked with to get a donation, the process has been driven to completion by a passionate engineer or group of engineers. You will have more success with a bottom-up approach rather than expecting your manager to make time for it. Leverage existing vendor relationships as much as possible. Getting a vendor approved at your company can require a large amount of effort on its own. Asking the company to onboard several different vendors to allow you to donate to several different projects is unlikely to succeed. Instead, focus your efforts on getting vendor approval for Open Collective and GitHub Sponsors. With Open Collective, you can set up a fund12 where you can transfer a lump-sum of money and then donate to individual projects from that fund; with GitHub, if your company is already paying for a GitHub organization, you can leverage that relationship by having donations appear on the monthly bill13. If your company already donates to an open source foundation, such as the Linux Foundation, they may not realize that those donations to not go directly to projects. For example, ESLint is part of the Open JS Foundation, however the Open JS Foundation does not provide any money directly to ESLint. Open source foundations exist to provide a legal infrastructure and technical resources to projects. These are important, but they are very different from providing funds to the projects. You may need to explain this to your company. Be patient with your company. Especially in large companies, progress is slow and changes happen with persistence over time rather than all at once. If your company has never even considered donating to an open source project then you’ll need to provide a lot of context and get a lot of signoffs. This is completely normal. Don’t take it personally, just realize that giving away money for some unknown benefit is not high on most company’s lists of priorities.Conclusion

In this post, I’ve explored all of the topics I’m aware of with regards to company open source sponsorships. As I’m sure you realize if you’re read this far, it can take a lot of work to get your company started with donations to open source projects. My own experience has taught me to allow at least six months from first contact until the first donation is made, so patience is key throughout this process.

Making sure you have a good story to tell, finding the budget, and the establishing the structure of the program are the three most important steps in this process. And once all of this has been established for one project, it’s easy to apply to the next project. Indeed, companies tend to increase donations after they’ve accomplished one because the infrastructure is already in place. Establishing your company’s open source sponsorship program is a fixed, upfront cost that then allows less friction for other donations going forward.

My hope is that we are moving towards a time when companies, especially those large corporations, who benefit from using open source software will also start seeing open source sponsorships as part of their core business activities. It’s this software, available for free, that allows small startups to grow into massive multinational companies. It would be nice for more companies to pay it back in some way.

References

Open Collective 

GitHub Sponsors 

Indeed FOSS Contributor Fund 

Microsoft’s Free and Open Source Software Fund 

Announcing the first Salesforce FOSS Contributor Fund Recipient 

Mozilla Open Source Support Awards 

Sloan Foundation Digital Technology 

Google Summer of Code 

Google Season of Docs 

Comcast Innovation Fund 

Open Collective Gift Cards 

Open Collective Funds 

Companies can now invest in open source with GitHub Sponsors 

[image error]
 •  0 comments  •  flag
Share on Twitter
Published on May 17, 2021 17:00

April 19, 2021

The lazy-loading property pattern in JavaScript

Traditionally, developers have created properties inside of JavaScript classes for any data that might be needed within an instance. This isn’t a problem for small pieces of data that are readily available inside of the constructor. However, if some data needs to be calculated before becoming available in the instance, you may not want to pay that cost upfront. For example, consider this class:

class MyClass { constructor() { this.data = someExpensiveComputation(); }}

Here, the data property is created as the result of performing some expensive computation. It may not be efficient to perform that calculation upfront if you aren’t sure the property will be used. Fortunately, there are several ways to defer these operations until later.

The on-demand property pattern

The easiest way to optimize performing an expensive operation is to wait until the data is needed before doing the computation. For example, you could use an accessor property with a getter to do the computation on demand, like this:

class MyClass { get data() { return someExpensiveComputation(); }}

In this case, your expensive computation isn’t happening until the first time someone reads the data property, which is an improvement. However, that same expensive computation is performed every time the data property is read, which is worse than previous example where at least the computation was performed just once. This isn’t a good solution, but you can build upon it to create a better one.

The messy lazy-loading property pattern

Only performing the computation when the property is accessed is a good start. What you really need is to cache the information after that point and just use the cached version. But where do you cache that information for easy access? The easiest approach is to define a property with the same name and set its value to the computed data, like this:

class MyClass { get data() { const actualData = someExpensiveComputation(); Object.defineProperty(this, "data", { value: actualData, writable: false, configurable: false, enumerable: false }); return actualData; }}

Here, the data property is once again defined as a getter on the class, but this time it caches the result. The call to Object.defineProperty() creates a new property called data that has a fixed value of actualData, and is set to not be writable, configurable, and enumerable (to match the getter). After that, the value itself is returned. The next time the data property is accessed, it will be reading from the newly created property rather than calling the getter:

const object = new MyClass();// calls the getterconst data1 = object.data;// reads from the data propertyconst data2 = object.data;

Effectively, all of the computation is done only the first time the data property is read. Each subsequent read of the data property is returning the cached the version.

The one downside to this pattern is that the data property starts out as a non-enumerable prototype property and ends up as a non-enumerable own property:

const object = new MyClass();console.log(object.hasOwnProperty("data")); // falseconst data = object.data;console.log(object.hasOwnProperty("data")); // true

While this distinction isn’t important in many cases, it is an important thing to understand about this pattern as it can cause subtle issues when the object is passed around. Fortunately, it’s easy to address this with an updated pattern.

The only-own lazy-loading property pattern for classes

If you have a use case where it’s important for the lazy-loaded property to always exist on the instance, then you can using Object.defineProperty() to create the property inside of the class constructor. It’s a little bit messier than the previous example, but it will ensure that the property only ever exists on the instance. Here’s an example:

class MyClass { constructor() { const instance = this; Object.defineProperty(this, "data", { get() { const actualData = someExpensiveComputation(); Object.defineProperty(instance, "data", { value: actualData, writable: false, configurable: false }); return actualData; }, configurable: true, enumerable: true }); }}

Here, the constructor creates the data accessor property using Object.defineProperty(). The property is created on the instance (by using this) and defines a getter as well as specifying the property to be enumerable and configurable (typical of own properties). It’s particularly important to set the data property as configurable so you can call Object.defineProperty() on it again.

The getter function then does the computation and calls Object.defineProperty() a second time. Note that the first argument is instance, because this has a different meaning inside of the getter function – it refers to the object on which the getter function is declared and not the instance of MyClass. The data property is now redefined as a data property with a specific value and is made non-writable and non-configurable to protect the final data. Then, the computed data is returned from the getter. The next time the data property is read, it will read from the stored value. As a bonus, the data property now only ever exists as an own property and acts the same both before and after the first read:

const object = new MyClass();console.log(object.hasOwnProperty("data")); // trueconst data = object.data;console.log(object.hasOwnProperty("data")); // true

For classes, this is most likely the pattern you want to use; object literals, on the other hand, can use a simpler approach.

The lazy-loading property pattern for object literals

If you are using an object literal instead of a class, the process is must simpler because getters defined on object literals are defined as enumerable own properties (not prototype properties) just like data properties. That means you can use the messy lazy-loading property pattern for classes without being messy:

const object = { get data() { const actualData = someExpensiveComputation(); Object.defineProperty(this, "data", { value: actualData, writable: false, configurable: false, enumerable: false }); return actualData; }};console.log(object.hasOwnProperty("data")); // trueconst data = object.data;console.log(object.hasOwnProperty("data")); // trueConclusion

The ability to redefine object properties in JavaScript allows a unique opportunity to cache information that may be expensive to compute. By starting out with an accessor property that is redefined as a data property, you can defer computation until the first time a property is read and then cache the result for later use. This approach works both for classes and for object literals, and is a bit simpler in object literals because you don’t have to worry about your getter ending up on the prototype.

One of the best ways to improve performance is to avoid doing the same work twice, so any time you can cache a result for use later, you’ll speed up your program. Techniques like the lazy-loading property pattern allow any property to become a caching layer to improve performance.

 •  0 comments  •  flag
Share on Twitter
Published on April 19, 2021 17:00

March 15, 2021

Two approaches to win an argument as a software engineer

If you’ve spent any time developing software professionally and then you are probably used to the spirited debates that take place between software engineers as well as between software engineers and management, design, and product. Software engineers are not known for being shy about their opinions on any particular subject, and especially when it comes to the company they work for or the software they work on. However, many software engineers are not good at convincing others of their position. The fundamental problem is in the approach.

When trying to convince someone that their position is correct, software engineers tend to default to a data transfer methodology: I have arrived at my position due to the state of data in my brain and so to convince you I will now attempt to transfer that state to your brain. The problem is, not everyone’s brain works the same way, so attempting to transfer that state is inefficient and can result in duplicating errors. Unfortunately, there are no checksums to fall back on.

Another approach software engineers try is an approach based on trust. If you trust me, then you should trust that what I say is true and should therefore agree with my recommendation. This approach is actually a power play where you believe that your clout is enough that people should have blind faith in you. This only works some of the time for some people and generally leaves everyone feeling resentful. You don’t want to steamroll your team members, you want collaboration.

So if data transfer and trust won’t work, what is left? The simple answer is to create a separate pool of data that exists outside of your brain. This “outside data” needs to be presented in a way that others can understand, so you are meeting together in the middle rather than trying to project your internal state to someone else.

That probably sounds very wordy, so let me break it down to two approaches: argue with data and argue with code.

Approach 1: Prove something is true with data

If you want to prove that something is true, which is to say that the thing you’re promoting is both factual and accurate, then the only way to win this argument is to gather data to illustrate the fact. For example, each of these statements may or may not be true on a project:

Switching to another framework will improve performance Our algorithm is more error prone than others so we should change it We waste a lot of time on repetitive tasks

Statements like these tend to start out as opinion and often don’t go any further, which leads to arguments of opinion. Opinion arguments can’t be won because, by definition, an opinion cannot be wrong. The only way to resolve this type of dispute is with data.

First, get specific and define all of your terms. What is it you are actually claiming? Taking the first statement, what does it mean to “improve performance?” Are you talking about reducing page load time? Or maybe reducing CPU utilization on a virtual machine? Or maybe using less memory? Be as specific as possible about what you are proposing as a fact.

Second, ask yourself what kind of data would prove your point? Have other companies published data showing the performance improvement between the framework you’re using and the one you’re proposing? Again, are you looking for page load time, CPU utilization, memory utilization, or something else? What numbers would make your point?

Third, do you already have the data you need or do you need to collect it? Some companies collect a lot of data about their software and operations; some do not. To get the data you need, you may need to add some instrumentation, or dive into your analytics system, or find tech talks or research papers. You need to find a source of data that is separate from you to make your point.

Keep in mind that data can be quantitative (measured values) or qualitative (opinions). While quantitative data is always preferable, it’s not always possible. In those moments, qualitative data can still win the argument. If you’re trying to prove that the team is wasting a lot of time on repetitive tasks, your best bet may be to have everyone on the team fill out a survey asking questions about the tasks they’re doing and if they are frustrated by them.

Last, present the data in a format that anyone can understand. Oftentimes, putting together a simple slide deck is still the easiest way to convince others (especially in management) that something is true. Just make sure you give people the time and space to consume the data you’ve presented before pushing for a decision.

The most important thing about this approach is that you are gathering data that exists outside of your brain and can easily be shared with others.

Approach 2: Prove something is possible with code

If you want to prove that something is possible, then the only way to win this argument is with code. It always surprises me when I find two software engineers arguing with each other over whether something is possible when it might take an hour to write some code and settle the issue permanently. It doesn’t make sense to argue over something that can be proven, beyond all doubt, by writing code. Here are some example statements that can probably be proved with code:

We can make the list scroll infinitely It’s possible to write a complex query with one request Switching to another framework will improve performance

Back when I was a young web developer, when people proposed an infinitely-scrolling list I thought they were crazy. It would never be smooth, it would never be fast enough, it would crash the browser due to memory limits. But then I saw the first demo of an infinitely-scrolling list that was smooth and didn’t crash the browser, and that was it. No more argument from my side. Just because I couldn’t figure out how to do it didn’t mean it wasn’t possible. If you’re in a position where you are arguing something is possible, go ahead and create it (or a reasonable prototype); if you are unwilling or unable to write the code, then it’s time to drop the argument.

One last point: that last statement looks familiar, doesn’t it? Yes, sometimes the same statement needs both data and code to win the argument. In this case, you might need to gather data to prove that performance is a problem and then also write some code to show that switching to the new framework will improve those numbers. You are actually trying to prove two things: 1) the performance is a problem and 2) switching to the new framework addresses the problem. The combination approach works exceedingly well in software engineering because so much of the work is about making changes and measuring the effect of those changes.

Conclusion

Arguing your point is an expected part of being a software engineer. It is fine to have opinions about everything, but if you are attempting to convince someone that your position is correct in order to make some change, then you owe it to them and to yourself to make sure you are correct. You shouldn’t expect people to blindly follow your opinions; you should expect to need to produce data, code, or some combination of the two to convince others. The willingness to do the research, data crunch, write code, or otherwise dig into the problem is what will ultimately convince others of your position.

No matter what kind of work-related argument you are trying to win, remember to argue with data and argue with code.

 •  0 comments  •  flag
Share on Twitter
Published on March 15, 2021 17:00

February 15, 2021

Introducing Env: a better way to read environment variables in JavaScript

If you write server-side JavaScript, chances are you’ve need to read information from environment variables. It’s considered a best practice to share sensitive information, such as access tokens, inside of environment variables to keep them secure. However, the way environment variables are read from JavaScript is error-prone in subtle ways that might take you hours to figure out. When an error occurs reading an environment variable, you want to know immediately, and you don’t want to interpret cryptic error messages. That’s where Env comes in.

Installing Env

Env1 is a zero-dependency utility designed to make reading environment variables safer and less error-prone. It does this by addressing the root causes of environment variable-related errors in server-side JavaScript. It works in both Node.js and Deno, and automatically reads environment variables from the correct location based on the runtime being used.

To use Env in Node.js, install it with npm:

$ npm install @humanwhocodes/env

And then import the Env constructor:

import { Env } from "@humanwhocodes/env";// orconst { Env } = require("@humanwhocodes/env");

To use Env in Deno, reference it from Skypack:

import { Env } from "https://cdn.skypack.dev/@humanwhocode...

Once you have the Env constructor, you can create a new instance like this:

const env = new Env();

And now you’re ready to read environment variables safely.

Problem #1: Missing environment variables

The first problem Env addresses is how to deal with missing environment variables. It’s quite common for environment variables to go missing either because they were accidentally not set up correctly or because they only exist on some containers and not all. In any case, you want to handle missing environment variables seamlessly. In Node.js, you might do something like this:

const USERNAME = process.env.USERNAME || "guest";

The intent here is to use the USERNAME environment variable if present, and if not, default to "guest". Env streamlines this to make setting defaults clear:

const USERNAME = env.get("USERNAME", "guest");

This code has the same effect but avoids any type coercion in the process. Of course, this assumes it’s okay for USERNAME to be missing. But what if you absolutely need an environment variable present in order for your application to work? For that, you might write some code like this:

const USERNAME = process.env.USERNAME;if (!USERNAME) { throw new Error("Environment variable USERNAME is missing.");}

That’s a lot of code for some simple validation, and if you have several required environment variables, you’ll end up repeating this pattern for each one. With Env, you can use the require() method:

const USERNAME = env.require("USERNAME");

If the environment variable USERNAME is missing in this example, then an error is thrown telling you so. You can also use the required property in a similar way:

const USERNAME = env.required.USERNAME;

This syntax allows you to avoid typing a string but will still throw an error if USERNAME is not present.

Problem #2: Typos

Another type of error that is common with environment variables are typos. Typos can be hard to spot when you are typing the same thing multiple times. For example, you might type something like this:

const USERNAME = process.env.USERRNAME;

Personally, I’ve spent hours tracking down bugs related to my incorrectly typing the name of the environment variable in my code. For whatever reason, I type the name of the variable correctly but not the environment variable name. If you want your JavaScript variables to have the same name as some required environment variables, you can use destructuring of the required property to only type the name once:

const { PORT, HOST} = env.required;

Here, two local variables, PORT and HOST, are created from the environment variables of the same name. If either environment variable is missing, an error is thrown.

Problem #3: Type mismatches

Another subtle type of error with environment variables are type mismatches. For instance, consider the following Node.js code:

const PORT = process.env.PORT || 8080;

This line, or something similar, appears in a lot of Node.js applications. Most of the time it doesn’t cause an issue…but it could. Can you spot the problem?

All environment variables are strings, so the JavaScript variable PORT is a string when the environment variable is present and a number if not. Using similar code in Deno threw an error2 that took me a while to figure out. It turned out that the Deno HTTP server required the port to be a number, so it worked fine locally but when I deployed it to Cloud Run, I received an error.

To solve this problem, Env converts all default values into strings automatically:

const PORT = env.get("PORT", 8080);console.log(typeof PORT === "string"); // always true

Even if you pass in a non-string value as the default, Env will convert it to a string to ensure that you only ever receive a string value when reading environment variables.

Problem #4: Fallback variables

Sometimes you might want to check several environment variables and only use a default if none of the environment variables are present. So you might have code that looks like this:

const PORT = process.env.PORT || process.env.HTTP_PORT || 8080;

You can make that a bit clearer using Env:

const PORT = env.first(["PORT", "HTTP_PORT"], 8080);

Using this code, Env returns a value from the first environment variable it finds. Similar to get(), first() allows you to pass in a default value to use if none of the environment variables are found, and that default value is automatically converted to a string. As an added error check, if the first argument isn’t an array or is an array with only one item, then an error is thrown.

Conclusion

Env is one of those utilities that has been so valuable to me that I sometimes forget to mention it. I’ve been using it in a number of personal projects for the past two years and it’s saved me a lot of time. Debugging errors related to environment variables isn’t anyone’s idea of fun, and I can’t count the times where I’ve been saved by an Env error. I hope you find it helpful, as well.

Env 

serve() error: “Uncaught InvalidData” 

 •  0 comments  •  flag
Share on Twitter
Published on February 15, 2021 16:00

January 18, 2021

Creating a JavaScript promise from scratch, Part 7: Unhandled rejection tracking

When promises were introduced in ECMAScript 2015, they had an interesting flaw: if a promise didn’t have a rejection handler and was later rejected, you would have no idea. The rejection silently occurred behind the scenes and, therefore, could easily be missed. The best practice of always attaching rejection handlers to promises emerged due to this limitation. Eventually, a way to detect unhandled promise rejections was added to ECMA-262 and both Node.js and web browsers implemented console warnings when an unhandled rejection occurred. In this post, I’ll walk through how unhandled rejection tracking works and how to implement it in JavaScript.

This is the seventh and final post in my series about creating JavaScript promises from scratch. If you haven’t already read the previous posts, I’d suggest you do before continuing on:

Part 1: Constructor Part 2: Resolving to a promise Part 3: then(), catch(), and finally() Part 4: Promise.resolve() and Promise.reject() Part 5: Promise.race() and Promise.any() Part 6: Promise.all() and Promise.allSettled()

As a reminder, this series is based on my promise library, Pledge. You can view and download all of the source code from GitHub.

Unhandled rejection tracking in browsers

While both Node.js and web browsers have ways of dealing with unhandled rejections, I’m going to focus on the web browser implementation because it is defined in the HTML specification1. Having a specification to work from makes it easier to understand what’s going on as opposed to the Node.js implementation which is custom (though still similar to web browsers). To start, suppose you have a promise defined like this:

const promise = new Promise((resolve, reject) => { reject(43);});

This promise doesn’t have a rejection handler defined and so when it’s rejected it ends up being tracked by the browser. Periodically, the browser checks its list of unhandled rejections and fires a unhandledrejection event on globalThis. The event handler receives an event object with a promise property containing the rejected promise and a reason property containing the rejection reason (43 in the case of this example). For example:

// called when an unhandled rejection occursglobalThis.onunhandledrejection = event => { console.log(event.promise); // get the promise console.log(event.reason); // get the rejection reason};

In addition to triggering the unhandledrejection event, the browser will output a warning to the console indicating that an unhandled rejection occurred. You can therefore choose to track unhandled rejections programmatically or keep your console open to see them as you’re developing.

Late-handled promise rejection

You may be wondering, what happens if a rejection handler is added at some later point in time? After all, you can add a rejection handler anytime between creation of the promise and the time when the promise is destroyed through garbage collection. You can, for instance, do this:

const promise = new Promise((resolve, reject) => { reject(43);});setTimeout(() => { promise.catch(reason => { console.error(reason); });}, 1000));

Here, a promise is created without a rejection handler initially and then adds one later. What happens in this case depends largely on the amount of time that has passed:

If the rejection handler is added before the browser decides to trigger unhandledrejection, then the event will not be triggered. If the rejection handler is added after the browser has triggered unhandledrejection, then a rejectionhandled event is triggered to let you know that the rejection is no longer unhandled.

It’s a little bit confusing, but basically, any promise that triggers an unhandledrejection event could potentially trigger a rejectionhandled event later. Therefore, you really need to listen for both events and track which promises remain, like this:

const rejections = new Map();// called when an unhandled rejection occursglobalThis.onunhandledrejection = ({ promise, reason }) => { rejections.set(promise, reason);};// called when an unhandled rejection occursglobalThis.onrejectionhandled = ({ promise }) => { rejections.delete(promise);};

This code tracks unhandled rejections using a map. When an unhandledrejection event occurs, the promise and rejection reason are saved to the map; when a rejectionhandled event occurs, the promise is deleted from the map. By periodically checking the contents of rejections, you can then track which rejections occurred without handlers.

Another quirk in the relationship between the unhandledrejection and rejectionhandled events is that you can prevent the rejectionhandled event from firing by adding a rejection handler inside of the onunhandledrejection event handler, like this:

// called when an unhandled rejection occursglobalThis.onunhandledrejection = ({ promise, reason }) => { promise.catch(() => {}); // make the rejection handled};// this will never be calledglobalThis.onrejectionhandled = ({ promise }) => { console.log(promise);};

In this case, the rejectionhandled event isn’t triggered because a rejection handler is added before it’s time for that event. The browser assumes that you know the promise is now handled and so there is no reason to trigger the rejectionhandled event.

Eliminating the console warning

As mentioned previously, the browser will output a warning to the console whenever an unhandled promise rejection occurs. This console warning occurs after the unhandledrejection event is fired, which gives you the opportunity to prevent the warning altogether. You can cancel the console warning by calling the preventDefault() method on the event object, like this:

globalThis.onunhandledrejection = event => { event.preventDefault();};

This event handler ensures that the console warning for the unhandled rejection will not happen. Suppressing the console warning is helpful in production where you don’t want to litter the console with additional information once you already know a promise was missing a rejection handler.

With that overview out of the way, it’s now time to discuss how to implement the same browser unhandled rejection tracking from scratch.

Implementing unhandled rejection tracking

The design for rejection tracking in the Pledge library closely follows the web browser approach. Because I didn’t want to mess with the globalThis object, I decided to add two static methods to the Pledge class to act as event handlers:

class Pledge { // other methods omitted for space static onUnhandledRejection(event) { // noop } static onRejectionHandled(event) { // noop } // other methods omitted for space}

The event object is an instance of PledgeRejectionEvent, which is defined like this:

class PledgeRejectionEvent { constructor(pledge, reason) { this.pledge = pledge; this.reason = reason; this.returnValue = true; } preventDefault() { this.returnValue = false; }}

I’ve included the preventDefault() method as well as the returnValue legacy property so either way of canceling the event will work.

Last, I created a RejectionTracker class to encapsulate most of the functionality. While this class isn’t described in any specification, I found it easier to wrap all of the functionality in this class. I then attached an instance of RejectionTracker to Pledge via a symbol property:

Pledge[PledgeSymbol.rejectionTracker] = new RejectionTracker();

In this way, I can always reach the rejection tracker from any instance of Pledge through this.constructor[PledgeSymbol.rejectionTracker]. It will become more apparent why this is important later in this post.

What does it mean for a promise to be handled?

ECMA-262 considers a promise to be handled if the promise’s then() method has been called (which includes catch() and finally(), both of which call then() behind the scenes). It actually doesn’t matter if you’ve attached a fulfillment handler, a rejection handler, or neither, so long as then() was called. Each call to then() creates a new promise which then becomes responsible for dealing with any fulfillment or rejection. Consider this example:

const promise1 = new Promise((resolve, reject) => { reject(43);});const promise2 = promise1.then(value => { console.log(value);});

Here, promise1 is considered handled because then() is called and a fulfillment handler is attached. When promise1 is rejected, that rejection is passed on to promise2, which is not handled. A browser would report the unhandled rejection from promise2 and disregard promise1. So, the browser isn’t really tracking all unhandled rejections, but rather, it’s tracking whether the last promise in a chain has any handlers attached.

How do you know if a promise is handled?

ECMA-262 describes two key features that enable rejection tracking:

The [[PromiseIsHandled]] internal property2 of every promise. This is a Boolean value indicating if the promise is handled. It starts out as false and is changed to true after then() is called. The HostPromiseRejectionTracker() operation3 is an abstract representation of a promise rejection tracker. ECMA-262 itself does not specify an algorithm for this operation; instead, it defers that to host environments to decide (host environments meaning browsers, Node.js, Deno, etc.).

The majority of the functionality related to these two features is contained the PerformPromiseThen() operation4 (discussed in part 3), which I’ve implemented as performPledgeThen():

function performPledgeThen(pledge, onFulfilled, onRejected, resultCapability) { assertIsPledge(pledge); if (!isCallable(onFulfilled)) { onFulfilled = undefined; } if (!isCallable(onRejected)) { onRejected = undefined; } const fulfillReaction = new PledgeReaction(resultCapability, "fulfill", onFulfilled); const rejectReaction = new PledgeReaction(resultCapability, "reject", onRejected); switch (pledge[PledgeSymbol.state]) { case "pending": pledge[PledgeSymbol.fulfillReactions].push(fulfillReaction); pledge[PledgeSymbol.rejectReactions].push(rejectReaction); break; case "fulfilled": { const value = pledge[PledgeSymbol.result]; const fulfillJob = new PledgeReactionJob(fulfillReaction, value); hostEnqueuePledgeJob(fulfillJob); } break; case "rejected": { const reason = pledge[PledgeSymbol.result]; // if the pledge isn't handled, track it with the tracker if (pledge[PledgeSymbol.isHandled] === false) { hostPledgeRejectionTracker(pledge, "handle"); } const rejectJob = new PledgeReactionJob(rejectReaction, reason); hostEnqueuePledgeJob(rejectJob); } break; default: throw new TypeError(`Invalid pledge state: ${pledge[PledgeSymbol.state]}.`); } // mark the pledge as handled pledge[PledgeSymbol.isHandled] = true; return resultCapability ? resultCapability.pledge : undefined;}

Regardless of what happens during the course of called performPledgeThen(), the pledge is always marked as handled before the end of the function. If the pledge is rejected, then hostPledgeRejectionTracker() is called with the pledge and a second argument of "handle". That second argument indicates that the rejection was handled and shouldn’t be tracked as an unhandled rejection.

The HostPromiseRejectionTracker() is also called by the RejectPromise() operation5 (also discussed in part 3), which I’ve implemented as rejectPledge():

export function rejectPledge(pledge, reason) { if (pledge[PledgeSymbol.state] !== "pending") { throw new Error("Pledge is already settled."); } const reactions = pledge[PledgeSymbol.rejectReactions]; pledge[PledgeSymbol.result] = reason; pledge[PledgeSymbol.fulfillReactions] = undefined; pledge[PledgeSymbol.rejectReactions] = undefined; pledge[PledgeSymbol.state] = "rejected"; // global rejection tracking if (pledge[PledgeSymbol.isHandled] === false) { hostPledgeRejectionTracker(pledge, "reject"); } return triggerPledgeReactions(reactions, reason);}

Here, the rejectPledge() function called hostPledgeRejectionTracker() with a second argument of "reject", indicating that the pledge was rejected and not handled. Remember, rejectPledge() is the function that is called by the reject argument that is passed in to executor function when creating a new promise, so at that point in time, the promise hasn’t had any handlers assigned. So, rejectPledge() is marking the pledge as unhandled, and if then() is later called to assign a handler, then it will bemarked as handled.

I’ve implemented hostPledgeRejectionTracker() as follows:

export function hostPledgeRejectionTracker(pledge, operation) { const rejectionTracker = pledge.constructor[PledgeSymbol.rejectionTracker]; rejectionTracker.track(pledge, operation);}

This is where attaching the rejection handler to the Pledge constructor is helpful. I’m able to get to the RejectionTracker instance and call the track() method to keep this function simple.

The RejectionTracker class

The RejectionTracker class is designed to encapsulate all of the rejection tracking functionality described in the HTML specification:

An environment settings object also has an outstanding rejected promises weak set and an about-to-be-notified rejected promises list, used to track unhandled promise rejections. The outstanding rejected promises weak set must not create strong references to any of its members, and implementations are free to limit its size, e.g. by removing old entries from it when new ones are added.


This description is a little bit confusing, so let me explain it. There are two different collections used to track rejections:

The “about-to-be-notified” rejected promises list is a list of promises that have been rejected and will trigger the unhandledrejection event. The outstanding rejected promises weak set is a collection of promises that had unhandled rejections and triggered the unhandledrejection event. These promises are tracked just in case they have a rejection handler added later, in which case the rejectionhandled event is triggered.

So these are the two collections the RejectionTracker needs to manage. Additionally, it manages a logger (typically console but can be overwritten for testing) and a timeout ID (which I’ll explain later in this post). Here’s what the class and constructor look like:

export class RejectionTracker { constructor(logger = console) { this.aboutToBeNotified = new Set(); this.outstandingRejections = new WeakSet(); this.logger = logger; this.timeoutId = 0; } track(pledge, operation) { // TODO }}

I chose to use a set for the “about-to-be-notified” promises list because it will prevent duplicates while allowing me to iterate through all of the promises contained within it. The outstanding rejections collection is implemented as a weak set, per the specification, which means there’s no way to iterate over the contents. That’s not a problem for how this collection is used in algorithm, however.

Implementing HostPromiseRejectionTracker()

The primary method is track(), and that implements the functionality described in the HTML specification for HostPromiseRejectionTracker()6, which is as follows:

Let script be the running script. If script’s muted errors is true, terminate these steps. Let settings object be script’s settings object. If operation is "reject", Add promise to settings object’s about-to-be-notified rejected promises list. If operation is "handle", If settings object’s about-to-be-notified rejected promises list contains promise, then remove promise from that list and return. If settings object’s outstanding rejected promises weak set does not contain promise, then return. Remove promise from settings object’s outstanding rejected promises weak set. Let global be settings object’s global object. Queue a global task on the DOM manipulation task source given global to fire an event named rejectionhandled at global, using PromiseRejectionEvent, with the promise attribute initialized to promise, and the reason attribute initialized to the value of promise’s [[PromiseResult]] internal slot.

The first three steps can be ignored for our purposes because they are just setting up variables. The fourth steps occurs when operation is "reject", at which point the promise that was rejected is added to the about-to-be-notified rejected promises list. That’s all that needs to happen at this point because a recurring check will later read that list to determine if any events need to be fired. The more interesting part is what happens when operation is "handle", meaning that a previously rejected promise now has a rejection handler added. Here are the steps using clearer language:

If promise is in the about-to-be-notified rejected promises list, that means the promise was rejected without a rejection handler but the unhandledrejection event has not yet been fired for that promise. Because of that, you can just remove promise from the list to ensure the event is never fired, and therefore, you’ll never need to fire a rejectionhandled event. Your work here is done. If the outstanding rejected promises weak set doesn’t contain promise, then there’s also nothing else to do here. The unhandledrejection event was never fired for promise so the rejectionhandled event should also never fire. There’s no more tracking necessary. If promise is in the outstanding rejected promises weak set, that means it has previously triggered the unhandledrejection event and you are now being notified that it is handled. That means you need to trigger the rejectionhandled event. For simplicity, you can read “queue a global task” as “run this code with setTimeout().”

After all of that explanation, here’s what it looks like in code:

export class RejectionTracker { constructor(logger = console) { this.aboutToBeNotified = new Set(); this.outstandingRejections = new WeakSet(); this.logger = logger; this.timeoutId = 0; } track(pledge, operation) { if (operation === "reject") { this.aboutToBeNotified.add(pledge); } if (operation === "handle") { if (this.aboutToBeNotified.has(pledge)) { this.aboutToBeNotified.delete(pledge); return; } if (!this.outstandingRejections.has(pledge)) { return; } this.outstandingRejections.delete(pledge); setTimeout(() => { const event = new PledgeRejectionEvent(pledge, pledge[PledgeSymbol.result]); pledge.constructor.onRejectionHandled(event); }, 0); } // not part of spec, need to toggle monitoring if (this.aboutToBeNotified.size > 0) { this.startMonitor(); } else { this.stopMonitor(); } } // other methods omitted for space}

The code closely mirrors the specification algorithm, ultimately resulting in the onRejectionHandled method being called on the Pledge constructor with an instance of PledgeReactionEvent. This event can’t be cancelled, so there’s no reason to check the returnValue property.

I did need to add a little bit of extra code at the end to toggle the monitoring of rejected promises. You only need to monitor the about-to-be-notified rejected promises list to know when to trigger the unhandledrejection event. (The outstanding promise rejections weak set doesn’t need to be monitored.) To account for that, and to save resources, I turn on the monitor when there is at least one item in the about-to-be-notified rejected promises list and turn it off otherwise.

The actual monitoring process is described in the HTML specification, as well, and is implemented as the startMonitor() method.

Monitoring for promise rejections

The HTML specification1 says that the following steps should be taken to notify users of unhandled promise rejections:

Let list be a copy of settings object’s about-to-be-notified rejected promises list. If list is empty, return. Clear settings object’s about-to-be-notified rejected promises list. Let global be settings object’s global object. Queue a global task on the DOM manipulation task source given global to run the following substep: For each promise p in list: If p’s [[PromiseIsHandled]] internal slot is true, continue to the next iteration of the loop. Let notHandled be the result of firing an event named unhandledrejection at global, using PromiseRejectionEvent, with the cancelable attribute initialized to true, the promise attribute initialized to p, and the reason attribute initialized to the value of p’s [[PromiseResult]] internal slot. If notHandled is false, then the promise rejection is handled. Otherwise, the promise rejection is not handled. If p’s [[PromiseIsHandled]] internal slot is false, add p to settings object’s outstanding rejected promises weak set.

The specification further says:

This algorithm results in promise rejections being marked as handled or not handled. These concepts parallel handled and not handled script errors. If a rejection is still not handled after this, then the rejection may be reported to a developer console.


So this part of the specification describes exactly how to determine when an unhandledrejection event should be fired and what effect, if any, it has on a warning being output to the console. However, the specification doesn’t say when this should take place, so browsers are free to implement it in the way they want. For the purposes of this post, I decided to use setInterval() to periodically check the about-to-be-notified rejected promises list. This code is encapsulated in the startMonitor() method, which you can see here:

export class RejectionTracker { // other methods omitted for space startMonitor() { // only start monitor once if (this.timeoutId > 0) { return; } this.timeoutId = setInterval(() => { const list = this.aboutToBeNotified; this.aboutToBeNotified = new Set(); if (list.size === 0) { this.stopMonitor(); return; } for (const p of list) { if (p[PledgeSymbol.isHandled]) { continue; } const event = new PledgeRejectionEvent(p, p[PledgeSymbol.result]); p.constructor.onUnhandledRejection(event); const notHandled = event.returnValue; if (p[PledgeSymbol.isHandled] === false) { this.outstandingRejections.add(p); } if (notHandled) { this.logger.error(`Pledge rejection was not caught: ${ p[PledgeSymbol.result] }`); } } }, 100); } stopMonitor() { clearInterval(this.timeoutId); this.timeoutId = 0; }}

The first step in stopMonitor() is to ensure that only one timer is ever used, so I check to make sure that timeoutId is 0 before proceeding. Next, list stores a reference to the current about-to-be-notified rejected promises list and then the property is overwritten with a new instance of Set to ensure that the same promises aren’t processed by this check more than once. If there are no promises to process then the monitor is stopped and the function exits (this is not a part of the specification).

Next, each pledge in list is evaluated. Remember that the PledgeSymbol.isHandled property indicates if there’s a rejection handler attached to the pledge, so if that is true, then you can safely skip processing that pledge. Otherwise, the Pledge.onUnhandledRejection() method is called with an event object. Unlike with Pledge.onRejectionHandled(), in this case you care about whether or not the event was cancelled, so notHandled is set to the event’s return value.

After that, the function checks PledgeSymbol.isHandled again because it’s possible that the code inside of Pledge.onUnhandledRejection() might have added a rejection handler. If this property is still false, then the pledge is added to the outstanding rejections weak set to track for any future rejection handler additions.

To finish up the algorithm, if notHandled is true, that’s when an error is output to the console. Keep in mind that the notHandled variable is the sole determinant of whether or not a console error is output; the PledgeSymbol.isHandled property is a completely separate value that only indicates if a rejection handler is present.

The stopMonitor() method simply cancels the timer and resets the timeoutId to 0.

With that, the RejectionTracker class is complete and all of the unhandled rejection tracking from browser implementations are now part of the Pledge library.

Wrapping Up

This post covered how browsers track unhandled promise rejections, which is a bit different than how Node.js tracks them. The browser triggers an unhandledrejection event when a rejected promise is missing a rejection handler as well as outputting a message to the console. If the promise later has a rejection handler assigned, then a rejectionhandled event is triggered.

The description of how this functionality works is spread across both the ECMA-262 and HTML specifications, with the former defining only a small, abstract API while the latter provides explicit instructions to browsers on how to track unhandled rejections.

All of the code from this series is available in the Pledge on GitHub. I hope you’ll download it and try it out to get a better understanding of promises.

And thank you to my sponsors, whose donations supported parts 5 through 7 of this series. If you enjoyed this series and would like to see more in-depth blog posts, please consider sponsoring me. Your support allows independent software developers like me to continue our work.

References

Unhandled promise rejections  ↩2

Properties of Promise Instances 

HostPromiseRejectionTracker ( promise, operation ) 

PerformPromiseThen ( promise, onFulfilled, onRejected [ , resultCapability ] ) 

RejectPromise ( promise, reason ) 

HostPromiseRejectionTracker(promise, operation) 

 •  0 comments  •  flag
Share on Twitter
Published on January 18, 2021 16:00

Nicholas C. Zakas's Blog

Nicholas C. Zakas
Nicholas C. Zakas isn't a Goodreads Author (yet), but they do have a blog, so here are some recent posts imported from their feed.
Follow Nicholas C. Zakas's blog with rss.