The canonical assembler is two-pass. On the first pass, it builds a symbol table. The basic entry of a symbol table has the name of the symbol, its type (constant or address), and the address (or value for a constant). Of course in order to know what address to put in for the symbol, you have to figure out the opcodes and how many bytes they take. You don't generate binary code on the first pass. Once you have completed the first pass, all the symbols should now be known and in the symbol table. On the second pass, you generate the code.
The reason for two passes is to look for forward references:
JMP LABEL399 . . LABEL399: .
When the assembler gets to the JMP, it has no idea what the value of the label is, so it has no idea what value to put in for the jump. You do know (usually) how many bytes the JMP takes so you just update your location counter (which keeps track of the current code address), add the label to the symbol table (marked as a forward reference) and keep going. When you hit the label and look it up, you can now fill in the symbol value and unmark it as a forward reference. At the end of pass 1, you scan the symbol table for entries still marked as a forward reference. These are errors. On pass 2, you can now generate the code.
Some assemblers use 1 pass. They generate the code but put place holders where forward reference values should go. They also have to keep track of where the forward reference appeared. That forward reference might be part of an expression, so you need to keep a reference to the expression (or to copy the expression somewhere) so you can evaluate the expression once the forward reference(s) is defined. When you can evaluate the forward reference, you go back and fix up all those places in the code where you put place holders.
Some CPUs have a long jump instruction which uses the full address of the destination and a shorter jump which uses an offset to the destination. Some assemblers have their own jump operation which uses the short one when possible. Such a thing makes it harder to do it in one pass (though probably not impossible, you just have to fixup all the symbols that come after it in the symbol table and backpatch any code that has already used those symbols).
As for books, the ones I have are probably out of print. You might try a search on Amazon.