JavaScript is a single-threaded programming language which means only one thing can happen at a time. That is, the JavaScript engine can only process one statement at a time in a single thread.
While the single-threaded languages simplify writing code because you don’t have to worry about the concurrency issues, this also means you can’t perform long operations such as network access without blocking the main thread.
Imagine requesting some data from an API. Depending upon the situation the server might take some time to process the request while blocking the main thread making the web page unresponsive.
That’s where asynchronous JavaScript comes into play. Using asynchronous JavaScript (such as callbacks, promises, and async/await), you can perform long network requests without blocking the main thread.
While it’s not necessary that you learn all these concepts to be an awesome JavaScript developer, it’s helpful to know 🙂
So without further ado, Let’s get started 🙂
Tip: Using Bit you can turn any JS code into an API you can share, use and sync across projects and apps to build faster and reuse more code. Give it a try.
Component Discovery and Collaboration · Bit
Bit is where developers share components and collaborate to build amazing software together. Discover components shared…
bit.dev
How Does Synchronous JavaScript Work?
Before we dive into asynchronous JavaScript, let’s first understand how the synchronous JavaScript code executes inside the JavaScript engine. For example:
const second = () => {
console.log('Hello there!');
}const first = () => {
console.log('Hi there!');
second();
console.log('The End');
}first();
To understand how the above code executes inside the JavaScript engine, we have to understand the concept of the execution context and the call stack (also known as execution stack).
Execution Context
An Execution Context is an abstract concept of an environment where the JavaScript code is evaluated and executed. Whenever any code is run in JavaScript, it’s run inside an execution context.
The function code executes inside the function execution context, and the global code executes inside the global execution context. Each function has its own execution context.
Call Stack
The call stack as its name implies is a stack with a LIFO (Last in, First out) structure, which is used to store all the execution context created during the code execution.
JavaScript has a single call stack because it’s a single-threaded programming language. The call stack has a LIFO structure which means that the items can be added or removed from the top of the stack only.
Let’s get back to the above code snippet and try to understand how the code executes inside the JavaScript engine.
const second = () => {
console.log('Hello there!');
}const first = () => {
console.log('Hi there!');
second();
console.log('The End');
}first();
So What’s Happening Here?
When this code is executed, a global execution context is created (represented by main()) and pushed to the top of the call stack. When a call to first() is encountered, it’s pushed to the top of the stack.
Next, console.log('Hi there!') is pushed to the top of the stack, when it finishes, it’s popped off from the stack. After it, we call second(), so the second() function is pushed to the top of the stack.
console.log('Hello there!') is pushed to the top of the stack and popped off the stack when it finishes. The second() function finishes, so it’s popped off the stack.
console.log(‘The End’) is pushed to the top of the stack and removed when it finishes. After it, the first() function completes, so it’s removed from the stack.
The program completes its execution at this point, so the global execution context(main()) is popped off from the stack.
How Does Asynchronous JavaScript Work?
Now that we have a basic idea about the call stack, and how the synchronous JavaScript works, let’s get back to the asynchronous JavaScript.
What is Blocking?
Let’s suppose we are doing an image processing or a network request in a synchronous way. For example:
Doing image processing and network request takes time. So when processImage() function is called, it’s going to take some time depending on the size of the image.
When the processImage() function completes, it’s removed from the stack. After that the networkRequest() function is called and pushed to the stack. Again it’s also going to take some time to finish execution.
At last when the networkRequest() function completes, greeting() function is called and since it contains only a console.log statement and console.log statements are generally fast, so the greeting() function is immediately executed and returned.
So you see, we have to wait until the function (such as processImage() or networkRequest()) has finished. This means these functions are blocking the call stack or main thread. So we can’t perform any other operation while the above code is executing which is not ideal.
So what’s the solution?
The simplest solution is asynchronous callbacks. We use asynchronous callbacks to make our code non-blocking. For example:
Here I have used setTimeout method to simulate the network request. Please keep in mind that the setTimeout is not a part of the JavaScript engine, it’s a part of something known as web APIs (in browsers) and C/C++ APIs (in node.js).
To understand how this code is executed we have to understand a few more concepts such event loop and the callback queue (also known as task queue or the message queue).
The event loop, the web APIs and the message queue/task queue are not part of the JavaScript engine, it’s a part of browser’s JavaScript runtime environment or Nodejs JavaScript runtime environment (in case of Nodejs). In Nodejs, the web APIs are replaced by the C/C++ APIs.
Now let’s get back to the above code and see how it’s executed in an asynchronous way.
When the above code loads in the browser, the console.log(‘Hello World’) is pushed to the stack and popped off the stack after it’s finished. Next, a call to networkRequest() is encountered, so it’s pushed to the top of the stack.
Next setTimeout() function is called, so it’s pushed to the top of the stack. The setTimeout() has two arguments: 1) callback and 2) time in milliseconds (ms).
The setTimeout() method starts a timer of 2s in the web APIs environment. At this point, the setTimeout() has finished and it’s popped off from the stack. After it, console.log('The End') is pushed to the stack, executed and removed from the stack after its completion.
Meanwhile, the timer has expired, now the callback is pushed to the message queue. But the callback is not immediately executed, and that’s where the event loop kicks in.
The Event Loop
The job of the Event loop is to look into the call stack and determine if the call stack is empty or not. If the call stack is empty, it looks into the message queue to see if there’s any pending callback waiting to be executed.
In this case, the message queue contains one callback, and the call stack is empty at this point. So the Event loop pushes the callback to the top of the stack.
After that the console.log(‘Async Code’) is pushed to the top of the stack, executed and popped off from the stack. At this point, the callback has finished so it’s removed from the stack and the program finally finishes.
DOM Events
The Message queue also contains the callbacks from the DOM events such as click events and keyboard events. For example:
In case of DOM events, the event listener sits in the web APIs environment waiting for a certain event (click event in this case) to happen, and when that event happens, then the callback function is placed in the message queue waiting to be executed.
Again the event loop checks if the call stack is empty and pushes the event callback to the stack if it’s empty and the callback is executed.
We have learned how the asynchronous callbacks and DOM events are executed which uses the message queue to store all the callbacks waiting to be executed.
ES6 Job Queue/ Micro-Task queue
ES6 introduced the concept of job queue/micro-task queue which is used by Promises in JavaScript. The difference between the message queue and the job queue is that the job queue has a higher priority than the message queue, which means that promise jobs inside the job queue/ micro-task queue will be executed before the callbacks inside the message queue.
Script start
Script End
Promise resolved
setTimeout
We can see that the promise is executed before the setTimeout, because promise response are stored inside the micro-task queue which has a higher priority than the message queue.
Let’s take another example, this time with two promises and two setTimeout. For example:
We can see that the two promises are executed before the callbacks in the setTimeout because the event loop prioritizes the tasks in micro-task queue over the tasks in message queue/task queue.
While the event loop is executing the tasks in the micro-task queue and in that time if another promise is resolved, it will be added to the end of the same micro-task queue, and it will be executed before the callbacks inside the message queue no matter for how much time the callback is waiting to be executed.
So all the tasks in micro-task queue will be executed before the tasks in message queue. That is, the event loop will first empty the micro-task queue before executing any callback in the message queue.
Conclusion
So we have learned how asynchronous JavaScript works and other concepts such as call stack, event loop, message queue/task queue and job queue/micro-task queue which together make the JavaScript runtime environment. While it’s not necessary that you learn all these concepts to be an awesome JavaScript developer, but it’s helpful to know these concepts 🙂
I have been doing day-to-day code reviews for over a decade now. The benefits of code reviews are plenty: someone spot checks your work for errors, they get to learn from your solution, and the collaboration helps to improve the organization’s overall approach to tooling and automation. If you’re not currently doing code reviews in your organization, start now. It’ll make everyone a better engineer.
Plenty of people and organizations have shared their code review best practices and what the definition of good code reviews mean to them. Guides from Google, the SmartBear team, and engineer Philipp Hauerare all excellent reads. Below is my personal take on what good code reviews look like and how to make them even better at the team and organizational level. This is in the context of the tech environment I have been working at – currently at Uber, and before that at Skype/Microsoft and Skyscanner.
Good code reviews are the bar that all of us should strive for. They cover common and easy to follow best practices that any team can get started with, while ensuring high-quality and helpful reviews for the long term.
Better code reviews are where engineers keep improving how they do code reviews. These code reviews look at the code change in the context of the codebase, of who is requesting it and in what situation. These reviews adjust their approach based on the context and situation. The goal not only being a high-quality review, but also to help the developers and teams requesting the review to be more productive.
Areas Covered by the Code Review
Good code reviews look at the change itself and how it fits into the codebase. They will look through the clarity of the title and description and “why” of the change. They cover the correctness of the code, test coverage, functionality changes, and confirm that they follow the coding guides and best practices. They will point out obvious improvements, such as hard to understand code, unclear names, commented out code, untested code, or unhandled edge cases. They will also note when too many changes are crammed into one review, and suggest keeping code changes single-purposed or breaking the change into more focused parts.
Better code reviews look at the change in the context of the larger system, as well as check that changes are easy to maintain. They might ask questions about the necessity of the change or how it impacts other parts of the system. They look at abstractions introduced and how these fit into the existing software architecture. They note maintainability observations, such as complex logic that could be simplified, improving test structure, removing duplications, and other possible improvements. Engineer Joel Kemp describes great code reviews as a contextual pass following an initial, light pass.
Tone of the Review
The tone of code reviews can greatly influence morale within teams. Reviews with a harsh tone contribute to a feeling of a hostile environment with their microaggressions. Opinionated language can turn people defensive, sparking heated discussions. At the same time, a professional and positive tone can contribute to a more inclusive environment. People in these environments are open to constructive feedback and code reviews can instead trigger healthy and lively discussions.
Good code reviews ask open-ended questions instead of making strong or opinionated statements. They offer alternatives and possible workarounds that might work better for the situation without insisting those solutions are the best or only way to proceed. These reviews assume the reviewer might be missing something and ask for clarification instead of correction.
Better code reviews are also empathetic. They know that the person writing the code spent a lot of time and effort on this change. These code reviews are kind and unassuming. They applaud nice solutions and are all-round positive.
Approving vs Requesting Changes
Once a reviewer completes their review, they can either mark it approved, block the review with change requests, or not set a specific status, leaving it in a “not yet approved” state. How reviewers use the approve and request changes statuses is telling of the code reviews.
Good code reviews don’t approve changes while there are open-ended questions. However, they make it clear which questions or comments are non-blocking or unimportant, marking them distinctively. They are explicit when approving a change – e.g. adding a thumbs up comment like “looks good!”. Some places use acronyms like LGTM—these also work, but be aware that newcomers could misinterpret these insider acronyms for something else. Good code reviews are equally explicit when they are requesting a follow-up, using the code review tool or team convention to communicate this.
Better code reviews are firm on the principle but flexible on the practice: sometimes, certain comments are addressed by the author with a separate, follow-up code change. For changes that are more urgent than others, reviewers try to make themselves available for quicker reviews.
From Code Reviews to Talking to Each Other
Code reviews are usually done asynchronously and in writing through a code review tool. This is usually out of convenience, to enable remote code reviews, and to allow multiple people to review the same code change. But when is it time to stop using the tool—however good it might be—and start talking face to face about the code?
Good code reviews leave as many comments and questions as are needed. If the revision does not address all of them, they will note those as well. When the conversation gets into a long back-and-forth, reviewers will try to switch to talking to the author in-person instead of burning more time using the code review tool.
Better code reviews will proactively reach out to the person making the change after they do a first pass on the code and have lots of comments and questions. These people have learned that they save a lot of time, misunderstandings, and hard feelings this way. The fact that there are many comments on the code indicates that there is likely some misunderstanding on either side. These kinds of misunderstandings are easier identified and resolved by talking things through.
Nitpicks
Nitpicks are are unimportant comments, where the code could be merged without even addressing these. These could be things like variable declarations being in alphabetical order, unit tests following a certain structure, or brackets being on the same line.
Good code reviews make it clear when changes are unimportant nitpicks. They usually mark comments like these distinctively, adding the “nit:” prefix to them. Too many of these can become frustrating and take the attention away from the more important parts of the review, so reviewers aim to not go overboard with these.
Better code reviews realize that too many nitpicks are a sign of lack of tooling or a lack of standards. Reviewers who come across these frequently will look at solving this problem outside the code review process. For example, many of the common nitpick comments can be solved via automated linting. Those that cannot can usually be resolved by the team agreeing to certain standards and following them—perhaps even automating them, eventually.
Code Reviews for New Joiners
Starting at a new company is overwhelming for most people. The codebase is new, the style of programming is different than before, and people review your code very differently. So should code reviews be gentler for new starters, to get them used to the new environment, or should they keep the bar just as high, as it is for everyone else?
Good code reviews use the same quality bar and approach for everyone, regardless of their job title, level or when they joined the company. Following the above, code reviews have a kind tone, request changes where needed, and will reach out to talk to reviewers when they have many comments.
Better code reviews pay additional attention to making the first few reviews for new joiners a great experience. Reviewers are empathetic to the fact that the recent joiner might not be aware of all the coding guidelines and might be unfamiliar with parts of the code. These reviews put additional effort into explaining alternative approaches and pointing to guides. They are also very positive in tone, celebrating the first few changes to the codebase that the author is suggesting.
Cross-Office, Cross-Time Zone Reviews
Code reviews get more difficult when reviewers are not in the same location. They are especially challenging when reviewers are sitting in very different time zones. I have had my fair share of these reviews over the years, modifying code owned by teams in the US and Asia, while being based in Europe.
Good code reviews account for the time zone difference when they can. Reviewers aim to review the code in the overlapping working hours between offices. For reviews with many comments, reviewers will offer to chat directly or do a video call to talk through changes.
Better code reviews notice when code reviews repeatedly run into timezone issues and look for a systemic solution, outside the code review framework. Let’s say a team from Europe is frequently changing a service that triggers code reviews from the US-based owner of this service. The system-level question is why these changes are happening so frequently. Are the changes done in the right codebase or should another system be changed? Will the frequency of changes be the same or go down over time? Assuming the changes are done in the right codebase and the frequency will not go down, can the cross-office dependency be broken in some way? Solutions to these kinds of problems are often not simple and could involve refactoring, creating of new services/interfaces or tooling improvements. But solving dependencies like this will make the life of both teams easier and their progress more efficient for the long term, meaning the return on investment is often quite impressive.
Organizational Support
The way companies and their engineering organizations approach code reviews is a big element of how efficient they can be. Organizations that view them as unimportant and trivial end up investing little in making reviews easier. In cultures like this, it might be tempting to just do away with code reviews entirely. Engineers advocating for doing better code reviews might feel isolated, without support from above and eventually give up. The result is an organization where problems continue to repeat and compound upon themselves.
Organizations with good code reviews ensure that all engineers take part in the code review process—even those that might be working on solo projects. They encourage raising the quality bar, and teams facilitate healthy discussions on code review approaches both at the team and org level. These companies often have code review guides for larger codebases that engineers initiated and wrote. Organizations like this recognise that code reviews take up a good chunk of engineers’ time. Many of these companies will add code reviews as expectations to the developer job competencies, expecting senior engineers to spend a larger chunk of their time reviewing the code of others.
Organizations with better code reviews have hard rules around no code making it to production without a code review—just as business logic changes don’t make it to production without automated tests. These organizations have learned that the cost of cutting corners is not worth it; instead, they have processes for expedited reviews for urgent cases. These organizations invest in developer productivity, including working continually to develop more efficient code reviews and tooling improvements. Helpful engineering executives don’t need convincing on the benefits of code reviews and other engineering best practices. Instead, they support initiatives on better tooling or more efficient code review processes that come from teams.
When people come across reviews that feel hostile, they feel they can speak up and have support all-round to resolve the issue. Senior engineers and managers consider code reviews that are not up to the bar just as much of an issue as sloppy code or poor behavior. Both engineers and engineering managers feel empowered to improve how code reviews are done.
Start With Good, Make it Better
Good code reviews already have lots of good effort going into them. They do a thorough review of the change itself, avoid being opinionated with the tone of comments, and make nitpicks clear. They maintain a consistent bar, regardless of who is requesting the review and try to make cross-time zone reviews less painful by paying additional attention to these. Organizations that have good reviews ensure that every developer regularly receives and does code reviews. This is already a high bar—but if you get here, don’t stop. Code reviews are one of the best ways to improve your skills, mentor others, and learn how to be a more efficient communicator.
Get to better code reviews by continuously improving on the details, but also start looking at changes at a high level as well. Be empathetic in the tone of comments and think of ways outside the code review process to eliminate frequent nitpicks. Make code reviews especially welcoming for new starters and look for systemic solutions for painful cross-time zone reviews. Organizations that are forward-looking encourage investing in tooling and process improvements to make code reviews better, reaping more of the benefits.
Is the freelancing life right for you? Here’s the good and the bad for you to consider.
Most full-time, salaried developers wonder what it would be like to go freelance one day. The lure of higher hourly rates and increased flexibility is appealing. Then doubts start to surface: How would I find clients? Would I have to do sales and marketing? How would I do my taxes? Amidst these doubts, the thought bubble fades away, and full-time salaried work seems like the safer option.
This article is for those who’re thinking more seriously about the possibility of going freelance. It’s for people who want to assemble solid answers to questions like “If I go freelance, how much should I charge?”
Before diving too deep down the rabbit hole, we’ll start by figuring out if freelance development is right for you. To start, we’ll go through the best aspects of freelancing, followed by aspects that are more challenging or difficult. Compare these pros and cons with your current full-time salaried role and decide which one sounds best to you. The information shared here is based on my experience working as both a subcontractor via a consultancy, and as an independent freelancer working solo.
Freelancing Positives
More flexibility. For many, this is the most appealing aspect of making the leap to a freelance career. While some crave the routine that working from an office Monday to Friday provides, others chafe at the commute, long hours, and repetitive days. Even though many freelance developers often work from client offices, the change in environment is much more frequent, truncated by the shorter length of freelance engagements compared to employment contracts. Other freelancers prefer to work from home, from a coworking space, or even becoming a digital nomad.
More autonomy. As the saying goes, freelancers are their own boss. You decide your hourly rate, where you’d like to work from, and how your business will run. You decide when to work and when not to work. Depending on the client, you may get much more control over the tech stack and approach to delivering a project than you would as part of a development team. However, this level of autonomy can be challenging for people who struggle to stay motivated and on-task without the peer pressure of a team environment.
The ability to shape work around your lifestyle. I once had a colleague who would take 3 months off every year to go surfing in Brazil, then spend the remaining 9 months of the year freelancing. Contract work allowed him the flexibility to do that, but his lifestyle would have been incompatible with most full-time salaried positions.
The potential to earn more per hour than you would as a salaried employee. Freelance developers can charge between $80 – $250 an hour depending on experience and skillset (and sometimes more), rates that not many salaried developers achieve in their lifetime. However, unpaid time including marketing, admin, contract negotiations, meetings with prospective clients, sick days, vacations, and lean periods without enough clients can take the shine off high hourly rates. In the end, freelancing is often not as lucrative as it appears on the surface, particularly when freelancers have a high rate of client turnover. Freelancers who can work with repeat clients or on multi-month freelance contracts have the best chance of exceeding their earning potential as a full-time salaried employee.
The potential to expand your business. Full-time salaried employees who become overwhelmed with too much work generally have only two options: ask for more support or work increasingly long hours. Freelancers who are overwhelmed with too much work have a potentially lucrative way to deal with the situation: hire a subcontractor. This is the way that many consultancy businesses get their start: with a talented freelancer bringing others onboard to take on some of the workload and earning a cut of the subcontractor’s rates.
Choose your tech stack. If your employer asks you to use a tool or programming language that you dislike, there’s not a lot you can do about it. As a freelancer, you can choose to only work with clients who’ll allow you to use your favorite programming language and tools. You’re more likely to be starting greenfields projects than working on an existing codebase, and can therefore avoid inheriting a pile of spaghetti-code. However, this may not be true when you’re getting established as a freelancer, or during lean-times, where you may need to do whatever work is available.
Choose your own way of working. As a full-time salaried developer, you’ve likely had to conform to your team or manager’s preferred way of working. This might include project management apps that are frustrating to work with, boring (and occasionally futile) estimation sessions, and needlessly complex record-keeping and reporting requirements. This is likely to still be true if you’re a freelancer who works on client sites or embedded in client teams. However, if you’re working alone, clients generally care more about what you do rather than how you do it (even if that means their project is managed across three or four barely legible Post-It notes).
Freelancing Negatives
At this point, freelancing is probably sounding pretty great! But hold on a minute, there are a number of things that are challenging and difficult about being a freelancer. These things may or may not be deal-breakers for you.
No benefits. Sick days, annual leave, work laptops, retirement contributions, employer-paid health insurance, parental leave, gym memberships, free lunches, performance bonuses, training budgets, a company car… forget about them. Freelancers generally don’t receive any benefits from their clients other than money in exchange for services. Despite this, freelancers still get sick, need vacations, need new laptops, need health insurance, need parental leave, and all the rest. The difference is in who pays for these things. You do. Now you can probably see why that shiny $100 an hour rate soon seems less like a luxury and more like a necessity.
Potentially higher and more complex taxes. Depending on your country of residence you may have different tax obligations as a freelancer compared to as an employee. In some countries, freelancers are taxed at a slightly higher rate than employees. You’ll likely have to do much more expense tracking and receipt management than you would as a salaried employee. For example, you’ll have to remember to deduct and keep receipts for a portion of your bills, office equipment, work-related training, travel, marketing expenses, and subscriptions. To make sure you understand what your tax obligations will be as a freelancer, I’d recommend talking to an accountant if you’re seriously thinking about making the switch.
Income instability. Freelancers often talk about the “feast or famine” phenomenon. Sometimes it feels like you have far too many clients to manage, while at other times, your inbox is empty and your days are spent watching reruns of Dr. Phil. As a result, your income can fluctuate, making it difficult to plan for recurring expenses. This instability is more common for freelancers who are still getting established and haven’t had time to build up a network of repeat clients. In general, the best antidote to income instability is time. With time, you’ll develop a regular client base that will help make your income more consistent and predictable.
Avoiding ‘Clients from Hell’. It’s tempting to think that freelancing will provide a permanent escape from bad bosses, but the existence of the Clients From Hell website suggests otherwise. Most clients are lovely, but occasionally you’ll end up with a client who tries to inflate the scope of the project, questions your decisions, or expects you to be available 24/7. Another common problem that many freelancers experience is late payment, and unfortunately, chasing up payments will become part of your job. Using a platform like CodementorX can mitigate some of these issues, as clients are pre-vetted before they’re allowed to work with you.
More non-development, non-tech tasks as an indie freelancer. A general rule for many independent freelancers is that you should expect about 50% of your time to be billable. The other 50% will involve marketing, talking to potential clients, preparing quotes and estimates, drawing up contracts, attending meetings, sending invoices, chasing up payment, updating social media, tweaking your portfolio website, networking, and doing general admin and bookkeeping. Notice that those 50% are all non-development tasks, meaning that 50% of your time is likely to be spent doing things that are completely unrelated to programming. Does that bother you? If so, you’d probably be happier subcontracting for an agency or consultancy, who will take care of most of these tasks for you. The trade-off here is that subcontractors working for an agency or consultancy are paid lower hourly rates and have less flexibility than independent freelancers, as the agency needs to take their cut (often 50% or more of what the client is paying for you). Sub-contractors also have less flexibility and are usually expected to work on-site and stick to the same hours as other employers.
Risk of isolation. For some of my friends, the biggest obstacle of going freelance is the fear of isolation. As an independent freelancer you’ll often be working from home, cafes, or coworking spaces where social interaction won’t happen incidentally. If you’re flying solo on a project, you won’t get to experience the daily interactions that occur when you’re part of a team. This is often extremely off-putting for people who love the social aspect of full-time salaried work. Even if you work on-site, freelance contracts can be as short as a few days or weeks, giving you little time to form long-lasting bonds with the people you work with. In a recent survey of freelancers in the UK, almost half of the respondents said they felt that freelancing was ‘lonely’ or ‘isolating’. It’s clear that freelancers need to take proactive steps to ensure that they get adequate, satisfying social contact in the absence of a permanent set of colleagues.
It can be hard to set and stick to boundaries. While working in an office provides a natural rhythm to the workday (as well as social pressure to avoid spending the whole day watching NBA highlights on YouTube), working from home, a cafe, or a coworking space requires more self-discipline. The first kind of discipline is the discipline to work when you’re supposed to, rather than goofing off all day. The second kind is the discipline to stop working when it’s time to stop. Nobody will enforce these boundaries except for you (and possibly your significant other!).
You won’t always be able to rely on coworkers to help when you get stuck. This is more true for freelancers who work alone. What happens if you get stuck? You generally can’t ask the client for help, as they may not be technical and they might expect you to have all the answers because they see you as an expert. Instead, you’ll need to build your own support network of developers you can turn to when you need help or advice. Even the most experienced developer benefits from being able to spitball and sanity check ideas with other developers. If you don’t already have a network like this, you can get help via Codementor.
If freelancing is right for you, what next?
You’ve weighed the pros and cons and decided that becoming a freelancer developer is the right move for you. Here’s what to do next.
The first step is to assess your current financial situation. What are your current financial obligations and monthly expenses? What’s the minimum income you need to make ends meet? A spreadsheet can be helpful here. Making the switch to freelancing, or any career change, follows a general rule: as the speed of your transition increases, your level of financial risk increases. You need to find the right balance between speed and risk, and this will depend on the urgency with which you want to leave full-time salaried employment, and your financial and family situation. You have various options depending on your need for speed, and appetite for risk:
Keep working full-time and freelance on evenings and weekends.This is the safest option financially, but will take a toll on other areas of your life as you work longer hours. This may be a good option if you’re young and single, but becomes more difficult if you have less energy to burn, or a family that you need to nurture. If you go this route the idea is to transition to freelancing by gradually reducing the days that you work at your full-time salaried job. If this isn’t possible then you might consider saving up three months of your salary and using that as runway to launch your freelancing career, tiding you over while you assemble your first batch of clients. This option is arguably the safest, but also the slowest, as you’ll have less time overall to work on establishing your freelance business.
Go part-time and work on developing your freelance business the rest of the time. This might involve scaling back to part-time hours, or finding a new part-time role. Your part-time role will help make ends meet while still allowing you time to work on developing your freelance business. However, part-time roles in development are relatively rare. It may be easier to negotiate a part-time arrangement with your current employer if they sense that you might otherwise have to leave. This option is a good balance between financial safety and speed of transition.
Take a sabbatical to work on your freelance business. Some employers will allow you to take a ‘sabbatical’, a.k.a several weeks or months of unpaid leave. This provides you with a window of opportunity to try out a freelance career. If you like it, you can make the sabbatical permanent. If you don’t, you can return to your previous role.
Quit your job as soon as possible and work on developing your freelance business. This is the riskiest option, but can be viable for people who have enough savings to make it work. If you have a significant other or a family, you’ll also need to gain their support for this move. One way to reduce the risk involved with this option is to reduce your expenses as much as possible.
Ready to Start Work
If you’ve decided that freelancing is right for you, and picked an option from the list above that matches your appetite for risk, it’s time to start taking practical steps to make this move a reality.
In my next post I’ll break down how to get your first few clients as a freelance developer. Stay tuned!
I also wrote about PHP‘s type system in the past, so it’s good to see some improvements are actually arriving in PHP‘s core.
Type variance is another topic worth its own blog post, but in short: you’ll be able use covariant return types –
class ParentType {}
class ChildType extends ParentType {}
class A
{
public function covariantReturnTypes(): ParentType
{ /* … */ }
}
class B extends A
{
public function covariantReturnTypes(): ChildType
{ /* … */ }
}
– and contravariant arguments.
class A
{
public function contraVariantArguments(ChildType $type)
{ /* … */ }
}
class B extends A
{
public function contraVariantArguments(ParentType $type)
{ /* … */ }
}
Moving on to some more core-level features: foreign function interface or “FFI” in short, allows us to call C code from userland. This means that PHP extensions could be written in pure PHP and loaded via composer.
It should be noted though that this is a complex topic. You still need C knowledge to be able to properly use this feature.
Another lower-level feature is preloading. It’s is an amazing addition to PHP‘s core, which can result in some significant performance improvements.
In short: if you’re using a framework, its files have to be loaded and linked on every request. Preloading allows the server to load PHP files in memory on startup, and have them permanently available to all subsequent requests.
The performance gain comes of course with a cost: if the source of preloaded files are changed, the server has to be restarted.
Do you want to know more? I wrote a dedicated post about preloading here.
Two new magic methods have been added: __serialize and __unserialize. The difference between these methods and __sleep and __wakeup is discussed in the RFC.
Libraries like Symfony’s var dumper rely heavily on the reflection API to reliably dump a variable. Previously it wasn’t possible to properly reflect references, resulting in these libraries relying on hacks to detect them.
PHP 7.4 adds the ReflectionReference class which solves this issue.
Internal changes have been made to how hashing libraries are used, so that it’s easier for userland to use them.
More specifically, a new function password_algos has been added which returns a list of all registered password algorithms.
Changes and deprecations
Besides new features, there are also lots of changes to the language. Most of these changes are non-breaking, though some might have an effect on your code bases.
Note that deprecation warnings aren’t per definition “breaking”, but merely a notice to the developer that functionality will be removed or changed in the future. It would be good not to ignore deprecation warnings, and to fix them right away; as it will make the upgrade path for PHP 8.0 more easy.
The ternary operator has some weird quirks in PHP. This RFCadds a deprecation warning for nested ternary statements. In PHP 8, this deprecation will be converted to a compile time error.
Previously, exceptions could not be thrown in __toString. They were prohibited because of a workaround for some old core error handling mechanisms, but Nikita pointed out that this “solution” didn’t actually solve the problem it tried to address.
This behaviour is now changed, and exceptions can be thrown from __toString.
Since the addition of the spread operator, there might be cases where you’d want to use array_merge like so:
$merged = array_merge(...$arrayOfArrays);
To support the edge case where $arrayOfArrays would be empty, both array_merge and array_merge_recursivenow allow an empty parameter list. An empty array will be returned if no input was passed.
Changes were made to proc_open so that it can execute programs without going through a shell. This is done by passing an array instead of a string for the command.
This RFC bundles lots of small deprecations, each with their own vote. Be sure to read a more detailed explanation on the RFC page, though here’s a list of deprecated things:
You should always take a look at the full UPGRADING documentwhen upgrading PHP versions.
Here are some changes highlighted:
Calling parent:: in a class without a parent is deprecated.
Calling var_dump on a DateTime or DateTimeImmutableinstance will no longer leave behind accessible properties on the object.
openssl_random_pseudo_bytes will throw an exception in error situations.
Attempting to serialise a PDO or PDOStatement instance will generate an Exception instead of a PDOException.
Calling get_object_vars() on an ArrayObject instance will return the properties of the ArrayObject itself, and not the values of the wrapped array or object. Note that (array)casts are not affected.
ext/wwdx has been deprecated.
RFC voting process improvements
This is technically not an update related to PHP 7.4, though it’s worth mentioning: the voting rules for RFC‘s have been changed.
There are many a times where we have a bunch of tasks to be executed sequentially. The examples are from File handling to calling databases multiple times based on the result of the previous call. Or calling multiple APIs in a sequence where one call is dependent on another.
Prior to introduction of async/await, many used callbacks alongside setTimeOut to simulated the behaviour they wanted (aka callback hell). Later on people started to use promises which made the code much more readable but they would end up in the same place when the number of calls where high (aka promise hell).
Async functions
A function in JavaScript is async when it operates asynchronously via the event loop, using an implicit promise to return its result. Furthermore, the type of its result should be an AsyncFuncton object.
This function is nothing but a combination of promises and generators. I will not going into details of generators, but they usually contains one or many yield keywords.
Now lets see the async function in action. Assume we have a function which returns a string:
function hi() {
return 'Hi from JavaScript';
}
hi(); // 'Hi from JavaScript'
If we put async in front of the function, then it no longer returns string, it will be a promise which is wrapped around the string value automatically.
async function hi() {
return 'Hi from JavaScript';
}
hi(); // Promise {<resolved>: "Hi from JavaScript"}
Now in order to get the value from the promise we act like before:
hi().then(console.log); // 'Hi from JavaScript'
You might be wondering how this can help to solve the promise hell. Just bare with me and we’ll get there step by step with examples so it’d be clear when we’re finished.
Await
The await makes JavaScript engine to wait until a promise is resolved/rejected and returns it’s result. This keyword can only be used inside an async function.
You might think since await forces the JavaScript engine to wait, it will have some cost on CPU. But that’s not the case because the engine can perform other scripts while waiting for the promise to get resolves/rejected. Plus this is way more elegant that using promises and .then.
Warning: If you try to invoke an async function using await inside a normal function, you will get a syntax error.
function doSomething() {
await hi(); // Uncaught SyntaxError: await is only valid in async function
}
A small catch
Most people who start working with async/await forget that they can’t invoke an async function on top level code. This is due to the fact that we can’t have await inside a normal function and the top level functions are normal by default.
let response = await hi(); // syntax error in top-level code
console.log(response);
What you can do however, is to wrap your code in an asyncIIFE(immediately invoked function execution) and call it right there:
(async () => {
let response = await hi();
console.log(response); // 'Hi from JavaScript'
...
})();
Update: As Nick Tyler mentioned in the comments, there is a stage 3 proposal to support await in top level code. So stay tuned and watch this space:
Error handling
As I said before, most async functions can be written as a normal function with promises. However, async functions are less error-prone when it comes to error handling. If an awaited call fails, the exception is automatically caught and the Error object will be propagated to the caller using the implicit return promise.
Prior to this, we had to reject the promise which was returned from the normal function and use a .catch in the caller. I’ve seen many places where the developers used a try/catch and throw a new exception which meant the stack trace would be reset.
async function hi() {
throw new Error("Whoops!");
};
async function doSomething() {
try {
let response = await hi();
return response;
} catch(err) {
console.log(err);
}
}
doSomething();
Or you can avoid the try/catch because the promise generated by the call to hi becomes rejected. Then simply use .catch to handle the error.
async function hi() {
throw new Error("Whoops!");
};
async function doSomething() {
let response = await hi();
return response;
}
doSomething().catch(err => {
console.log(err);
});
You can ignore the catch all together and handle all the exceptions using a global exception handler if you think that’s more suitable to your situation. Something like this which uses the onrejectionhandled property of WindowsEventHandlers mixin.
If an error occurs, it propagates as usual, from the failed promise to Promise.all and then turns to an exception that you can catch using any of the above methods.
await can take in a “thenable”
Similar to promise.then, if you have any object which has a .then method, await will accepts it. This is to support scenarios where a 3rd-party object which is not a promise, but promise-compatible (it supports .then), it would be enough to use it with await.
You can have an async class method. Just prepend it with async and you’re good to go.
class Order {
async deliver() {
return await Promise.resolve('Pizza');
}
}
new Order()
.delivery()
.then(console.log); // Pizza
Summary
Just to quickly go through what we discussed so far:
async keyword makes a method asynchronous, which in turn always returns a promise and allows await to be used.
await keyword before a promise makes JavaScript wait until that is resolved/rejected. If the promise is rejected, an exception is generated, otherwise the result is returned.
Together, they provide a great opportunity for us to write clean, more testable, asynchronous code.
With async/await you wouldn’t need .then/.catch, but just note that they are still based on promises.
You can use Promise.all to wait for multiple async functions calls.
You can have an async method in a class.
I know there are many great articles around async/await, but I tried to cover some items where I had to constantly remind myself of. Hope it will help you to have a centralised place for most of what you need to write clean asynchronous JavaScript.