# Macro that doesn't typeset as it goes

\documentclass{article}
\usepackage{amsmath}

\newif\ifprime
\newcount\numOfPrimes \newcount\i \newcount\n \newcount\res \newcount\limit
\def\testdivision{%
\res=\n \divide\res by\i \multiply\res by\i
\def\isprime{{% <-- group
\i=3 \limit=\n \divide\limit by2 \global\primetrue
\def\storeprime#1{%
\else\ifnum\numOfPrimes=1 \addtolist#1{~and~}%the last prime to print
\fi\fi
\def\storeifprime#1{%
\isprime%test if n is prime
\ifprime \storeprime#1\fi}
\def\makelistofprimes#1#2{%
\def#1{}% initialize
\fi\fi}
\expandafter\def\expandafter#1\expandafter{#1#2}%
}

\begin{document}

\makelistofprimes\mylist{30}

\show\mylist % show the result on the terminal

The first 30 primes numbers are \mylist

\end{document}


Some of the macros in your code have been modified to accept as argument the name of the list to be populated. The key is \addtolist that, with a suitable chain of \expandafter tokens will cause expansion of the list to its previous value. A similar chain is needed for expanding \number when adding a found prime.

One could think to \def\addtolist#1#2{\edef#1{#1#2}}, but this would break the usage of ~.

Output on the terminal:

> \mylist=macro:
->2,~3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101, 103, 107, 109~and~113.


An implementation with a more efficient algorithm based on the sieve of Eratosthenes:

\documentclass{article}

% Rosser, p_n < n(log n + 2 log log n) for n ≥ 2
% J. B. Rosser, The n-th prime is greater than n log n,
% Proc. London Math. Soc., ser. 2, 45 (1939), 21–44

% Sieve of Eratosthenes algorithm taken from
% https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Pseudocode

\ExplSyntaxOn

\NewDocumentCommand{\makelistofprimes}{smm}
{% #1 = name of the list, #2 = number of primes to generate
\seq_clear_new:c { l_eratosthenes_#2_seq }
\int_compare:nTF { #3 = 1 }
{ \seq_put_right:cn { l_eratosthenes_#2_seq } { 2 } }
{ \__eratosthenes_list:nn { #2 } { #3 } }
\IfBooleanT {#1} { \seq_use:cn { l_eratosthenes_#2_seq } {,~} }
}

\int_new:N \l__eratosthenes_count_int
\int_new:N \l__eratosthenes_step_int

\cs_new_protected:Nn \__eratosthenes_list:nn
{
\intarray_new:cn { g__eratosthenes_#1_intarray }
{ \fp_eval:n { ceil(#2*(ln(#2)+2*ln(ln(#2)))) } }
\int_set:Nn \l__eratosthenes_count_int { \intarray_count:c { g__eratosthenes_#1_intarray } }
\intarray_gset:cnn { g__eratosthenes_#1_intarray } { 1 } { 1 } % 1 is not prime
\int_step_inline:nnn { 2 } { \fp_eval:n { floor( sqrt(\l__eratosthenes_count_int) ) } }
{
\int_compare:nT { \intarray_item:cn { g__eratosthenes_#1_intarray } { ##1 } = 0 }
{% the current number is prime
\int_step_inline:nnnn { ##1*##1 } { ##1 } { \l__eratosthenes_count_int }
{ \intarray_gset:cnn { g__eratosthenes_#1_intarray } { ####1 } { 1 } }
}
}
\int_zero:N \l__eratosthenes_step_int
\int_while_do:nn { \seq_count:c { l_eratosthenes_#1_seq } < #2 }
{
\int_incr:N \l__eratosthenes_step_int
\int_compare:nT { \intarray_item:cn { g__eratosthenes_#1_intarray } { \l__eratosthenes_step_int } = 0 }
{
\seq_put_right:cx { l_eratosthenes_#1_seq } { \int_eval:n { \l__eratosthenes_step_int } }
}
}
}

\ExplSyntaxOff

\begin{document}

The first 100 prime numbers are \makelistofprimes*{x}{100}

The first 10 prime numbers are \makelistofprimes*{y}{10}

\end{document}


This saves the list in a sequence that can then be reused in several ways. For implementation reasons, a new name is required for every call (this might be changed). The idea is to allocate an array of integers, but this requires to specify the number of items in it, so I use a bound on the n-th prime found in the cited article. The array is initially populated with zeros, so it’s simpler to use 1 as the mark for a nonprime.

Next the sieve is implemented as in the referenced Wikipedia page, with the only difference that 1 and 0 are switched. Finally a sequence is populated by traversing the array, picking up primes until we reach n of them.